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-24 00:33:29 -05:00
} , { "./constants" : 3 , "./utils" : 26 } ] , 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-02-10 01:34:23 -05:00
// -----------------
// Log Export
// -----------------
FFZ . ffz _commands . log = function ( room , args ) {
this . _pastebin ( this . _log _data . join ( "\n" ) , function ( url ) {
if ( ! url )
return this . room _message ( room , "There was an error uploading the FrankerFaceZ log." ) ;
this . room _message ( room , "Your FrankerFaceZ log has been pasted to: " + url ) ;
} ) ;
} ;
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
// -----------------
2015-02-10 01:34:23 -05:00
FFZ . ffz _commands . massunmod = function ( room , args ) {
2015-01-16 17:45:37 -05:00
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." ;
}
2015-02-10 01:34:23 -05:00
FFZ . ffz _commands . massunmod . help = "Usage: /ffz massunmod <list, of, users>\nBroadcaster only. Unmod all the users in the provided list." ;
2015-01-16 17:45:37 -05:00
2015-02-10 01:34:23 -05:00
FFZ . ffz _commands . massmod = function ( room , args ) {
2015-01-16 17:45:37 -05:00
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." ;
}
2015-02-10 01:34:23 -05:00
FFZ . ffz _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>' ,
2015-02-24 00:33:29 -05:00
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>' ,
EMOTE : '<svg class="svg-emote" height="16px" version="1.1" viewBox="0 0 18 18" width="16px" x="0px" y="0px"><path clip-rule="evenodd" d="M9,18c-4.971,0-9-4.029-9-9s4.029-9,9-9s9,4.029,9,9S13.971,18,9,18z M14,4.111V4h-0.111C12.627,2.766,10.904,2,9,2C7.095,2,5.373,2.766,4.111,4H4v0.111C2.766,5.373,2,7.096,2,9s0.766,3.627,2,4.889V14l0.05-0.051C5.317,15.217,7.067,16,9,16c1.934,0,3.684-0.783,4.949-2.051L14,14v-0.111c1.234-1.262,2-2.984,2-4.889S15.234,5.373,14,4.111zM11,6h2v4h-2V6z M12.535,12.535C11.631,13.44,10.381,14,9,14s-2.631-0.56-3.536-1.465l0.707-0.707C6.896,12.553,7.896,13,9,13s2.104-0.447,2.828-1.172L12.535,12.535z M5,6h2v4H5V6z" 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 ;
// -----------------------
2015-02-24 00:33:29 -05:00
// Developer Mode
2015-01-20 01:53:18 -05:00
// -----------------------
2015-02-24 00:33:29 -05:00
FFZ . settings _info . developer _mode = {
type : "boolean" ,
value : false ,
storage _key : "ffzDebugMode" ,
visible : function ( ) { return this . settings . developer _mode || ( Date . now ( ) - parseInt ( localStorage . ffzLastDevMode || "0" ) ) < 604800000 ; } ,
category : "Debugging" ,
name : "Developer Mode" ,
help : "Load FrankerFaceZ from the local development server instead of the CDN. Please refresh after changing this setting." ,
on _update : function ( ) {
localStorage . ffzLastDevMode = Date . now ( ) ;
}
} ;
2015-02-10 01:34:23 -05:00
FFZ . ffz _commands . developer _mode = function ( room , args ) {
2015-01-20 01:53:18 -05:00
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-24 00:33:29 -05:00
return "Developer Mode is currently " + ( this . settings . developer _mode ? "enabled." : "disabled." ) ;
2015-01-20 01:53:18 -05:00
2015-02-24 00:33:29 -05:00
this . settings . set ( "developer_mode" , enabled ) ;
2015-01-20 01:53:18 -05:00
return "Developer Mode is now " + ( enabled ? "enabled" : "disabled" ) + ". Please refresh your browser." ;
}
2015-02-10 01:34:23 -05:00
FFZ . ffz _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.
2015-02-24 00:33:29 -05:00
try {
Chat . create ( ) . destroy ( ) ;
} catch ( err ) { }
2015-01-20 01:53:18 -05:00
// 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 ) ;
2015-02-10 01:34:23 -05:00
try {
view . $ ( '.textarea-contain' ) . append ( this . build _ui _link ( view ) ) ;
} catch ( err ) {
this . error ( "setup: build_ui_link: " + err ) ;
}
2015-01-20 01:53:18 -05:00
}
}
// --------------------
// Modify Chat View
// --------------------
FFZ . prototype . _modify _cview = function ( view ) {
var f = this ;
view . reopen ( {
didInsertElement : function ( ) {
this . _super ( ) ;
2015-02-10 01:34:23 -05:00
try {
this . $ ( ) && this . $ ( '.textarea-contain' ) . append ( f . build _ui _link ( this ) ) ;
} catch ( err ) {
f . error ( "didInsertElement: build_ui_link: " + err ) ;
}
2015-01-20 01:53:18 -05:00
} ,
willClearRender : function ( ) {
this . _super ( ) ;
2015-02-10 01:34:23 -05:00
try {
this . $ ( ".ffz-ui-toggle" ) . remove ( ) ;
} catch ( err ) {
f . error ( "willClearRender: remove ui link: " + err ) ;
}
2015-01-20 01:53:18 -05:00
} ,
ffzUpdateLink : Ember . observer ( 'controller.currentRoom' , function ( ) {
2015-02-10 01:34:23 -05:00
try {
f . update _ui _link ( ) ;
} catch ( err ) {
f . error ( "ffzUpdateLink: update_ui_link: " + err ) ;
}
2015-01-20 01:53:18 -05:00
} )
} ) ;
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 ,
2015-02-10 01:34:23 -05:00
utils = require ( "../utils" ) ,
2015-01-20 20:25:26 -05:00
reg _escape = function ( str ) {
return str . replace ( /[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g , "\\$&" ) ;
2015-02-24 00:33:29 -05:00
} ,
SEPARATORS = "[\\s`~<>!-#%-\\x2A,-/:;\\x3F@\\x5B-\\x5D_\\x7B}\\u00A1\\u00A7\\u00AB\\u00B6\\u00B7\\u00BB\\u00BF\\u037E\\u0387\\u055A-\\u055F\\u0589\\u058A\\u05BE\\u05C0\\u05C3\\u05C6\\u05F3\\u05F4\\u0609\\u060A\\u060C\\u060D\\u061B\\u061E\\u061F\\u066A-\\u066D\\u06D4\\u0700-\\u070D\\u07F7-\\u07F9\\u0830-\\u083E\\u085E\\u0964\\u0965\\u0970\\u0AF0\\u0DF4\\u0E4F\\u0E5A\\u0E5B\\u0F04-\\u0F12\\u0F14\\u0F3A-\\u0F3D\\u0F85\\u0FD0-\\u0FD4\\u0FD9\\u0FDA\\u104A-\\u104F\\u10FB\\u1360-\\u1368\\u1400\\u166D\\u166E\\u169B\\u169C\\u16EB-\\u16ED\\u1735\\u1736\\u17D4-\\u17D6\\u17D8-\\u17DA\\u1800-\\u180A\\u1944\\u1945\\u1A1E\\u1A1F\\u1AA0-\\u1AA6\\u1AA8-\\u1AAD\\u1B5A-\\u1B60\\u1BFC-\\u1BFF\\u1C3B-\\u1C3F\\u1C7E\\u1C7F\\u1CC0-\\u1CC7\\u1CD3\\u2010-\\u2027\\u2030-\\u2043\\u2045-\\u2051\\u2053-\\u205E\\u207D\\u207E\\u208D\\u208E\\u2329\\u232A\\u2768-\\u2775\\u27C5\\u27C6\\u27E6-\\u27EF\\u2983-\\u2998\\u29D8-\\u29DB\\u29FC\\u29FD\\u2CF9-\\u2CFC\\u2CFE\\u2CFF\\u2D70\\u2E00-\\u2E2E\\u2E30-\\u2E3B\\u3001-\\u3003\\u3008-\\u3011\\u3014-\\u301F\\u3030\\u303D\\u30A0\\u30FB\\uA4FE\\uA4FF\\uA60D-\\uA60F\\uA673\\uA67E\\uA6F2-\\uA6F7\\uA874-\\uA877\\uA8CE\\uA8CF\\uA8F8-\\uA8FA\\uA92E\\uA92F\\uA95F\\uA9C1-\\uA9CD\\uA9DE\\uA9DF\\uAA5C-\\uAA5F\\uAADE\\uAADF\\uAAF0\\uAAF1\\uABEB\\uFD3E\\uFD3F\\uFE10-\\uFE19\\uFE30-\\uFE52\\uFE54-\\uFE61\\uFE63\\uFE68\\uFE6A\\uFE6B\\uFF01-\\uFF03\\uFF05-\\uFF0A\\uFF0C-\\uFF0F\\uFF1A\\uFF1B\\uFF1F\\uFF20\\uFF3B-\\uFF3D\\uFF3F\\uFF5B\\uFF5D\\uFF5F-\\uFF65]" ,
SPLITTER = new RegExp ( SEPARATORS + "*," + SEPARATORS + "*" ) ,
quote _attr = function ( attr ) {
return ( attr + '' )
. replace ( /&/g , "&" )
. replace ( /'/g , "'" )
. replace ( /"/g , """ )
. replace ( /</g , "<" )
. replace ( />/g , ">" ) ;
} ,
TWITCH _BASE = "http://static-cdn.jtvnw.net/emoticons/v1/" ,
build _srcset = function ( id ) {
return TWITCH _BASE + id + "/1.0 1x, " + TWITCH _BASE + id + "/2.0 2x, " + TWITCH _BASE + id + "/3.0 4x" ;
} ,
data _to _tooltip = function ( data ) {
var output = "<tr><td>Emoticon</td><td>" + data . code + "</td></tr>" ,
set = data . set ,
set _type = data . set _type ;
if ( set _type === undefined )
set _type = "Channel" ;
if ( ! set )
return data . code ;
else if ( set == "00000turbo" || set == "turbo" ) {
set = "Twitch Turbo" ;
set _type = null ;
}
if ( ! set _type )
output += '<tr><td class="center" colspan="2">' + set + '</td></tr>' ;
else
output += "<tr><td>" + set _type + "</td><td>" + set + "</td></tr>" ;
return '<table class="emote-data">' + output + '</table>' ;
} ,
build _tooltip = function ( id ) {
var emote _data = this . _twitch _emotes [ id ] ,
set = emote _data ? emote _data . set : null ;
if ( ! emote _data )
return "???" ;
if ( typeof emote _data == "string" )
return emote _data ;
if ( emote _data . tooltip )
return emote _data . tooltip ;
return emote _data . tooltip = data _to _tooltip ( emote _data ) ;
} ,
load _emote _data = function ( id , code , success , data ) {
if ( ! success )
return ;
if ( code )
data . code = code ;
this . _twitch _emotes [ id ] = data ;
var tooltip = build _tooltip . bind ( this ) ( id ) ;
var images = document . querySelectorAll ( 'img[emote-id="' + id + '"]' ) ;
for ( var x = 0 ; x < images . length ; x ++ )
images [ x ] . title = tooltip ;
2015-01-20 20:25:26 -05:00
} ;
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 ,
2015-02-10 01:34:23 -05:00
category : "Chat" ,
2015-02-08 02:01:09 -05:00
visible : function ( ) { return ! this . has _bttv } ,
name : "Username Capitalization" ,
help : "Display names in chat with proper capitalization."
} ;
2015-02-24 00:33:29 -05:00
FFZ . settings _info . banned _words = {
type : "button" ,
value : [ ] ,
category : "Chat" ,
visible : function ( ) { return ! this . has _bttv } ,
name : "Banned Words" ,
help : "Set a list of words that will be locally removed from chat messages." ,
method : function ( ) {
var old _val = this . settings . banned _words . join ( ", " ) ,
new _val = prompt ( "Banned Words\n\nPlease enter a comma-separated list of words that you would like to be removed from chat messages." , old _val ) ;
if ( new _val === null || new _val === undefined )
return ;
new _val = new _val . trim ( ) . split ( SPLITTER ) ;
var vals = [ ] ;
for ( var i = 0 ; i < new _val . length ; i ++ )
new _val [ i ] && vals . push ( new _val [ i ] ) ;
if ( vals . length == 1 && vals [ 0 ] == "disable" )
vals = [ ] ;
this . settings . set ( "banned_words" , vals ) ;
}
} ;
2015-02-08 02:01:09 -05:00
FFZ . settings _info . keywords = {
type : "button" ,
value : [ ] ,
2015-02-10 01:34:23 -05:00
category : "Chat" ,
2015-02-08 02:01:09 -05:00
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 ) ;
2015-02-10 01:34:23 -05:00
if ( new _val === null || new _val === undefined )
2015-02-08 02:01:09 -05:00
return ;
// Split them up.
2015-02-24 00:33:29 -05:00
new _val = new _val . trim ( ) . split ( SPLITTER ) ;
var vals = [ ] ;
for ( var i = 0 ; i < new _val . length ; i ++ )
new _val [ i ] && vals . push ( new _val [ i ] ) ;
2015-02-08 02:01:09 -05:00
2015-02-24 00:33:29 -05:00
if ( vals . length == 1 && vals [ 0 ] == "disable" )
vals = [ ] ;
2015-02-08 02:01:09 -05:00
2015-02-24 00:33:29 -05:00
this . settings . set ( "keywords" , vals ) ;
2015-02-08 02:01:09 -05:00
}
} ;
2015-02-10 01:34:23 -05:00
FFZ . settings _info . fix _color = {
type : "boolean" ,
value : false ,
category : "Chat" ,
visible : function ( ) { return ! this . has _bttv } ,
name : "Adjust Username Colors" ,
help : "Ensure that username colors contrast with the background enough to be readable." ,
on _update : function ( val ) {
if ( this . has _bttv )
return ;
document . body . classList . toggle ( "ffz-chat-colors" , val ) ;
}
} ;
2015-02-08 02:01:09 -05:00
FFZ . settings _info . chat _rows = {
type : "boolean" ,
value : false ,
2015-02-10 01:34:23 -05:00
category : "Chat" ,
2015-02-08 02:01:09 -05:00
visible : function ( ) { return ! this . has _bttv } ,
name : "Chat Line Backgrounds" ,
help : "Display alternating background colors for lines in chat." ,
on _update : function ( val ) {
2015-02-10 01:34:23 -05:00
if ( this . has _bttv )
return ;
document . body . classList . toggle ( "ffz-chat-background" , val ) ;
2015-02-08 02:01:09 -05:00
}
} ;
2015-01-20 01:53:18 -05:00
// ---------------------
// Initialization
// ---------------------
FFZ . prototype . setup _line = function ( ) {
2015-02-10 01:34:23 -05:00
// Chat Enhancements
document . body . classList . toggle ( "ffz-chat-colors" , ! this . has _bttv && this . settings . fix _color ) ;
document . body . classList . toggle ( 'ffz-chat-background' , ! this . has _bttv && this . settings . chat _rows ) ;
this . _colors = { } ;
2015-02-08 02:01:09 -05:00
this . _last _row = { } ;
2015-02-10 01:34:23 -05:00
var s = this . _fix _color _style = document . createElement ( 'style' ) ;
s . id = "ffz-style-username-colors" ;
s . type = 'text/css' ;
document . head . appendChild ( s ) ;
2015-02-24 00:33:29 -05:00
// Emoticon Data
this . _twitch _emotes = { } ;
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-02-10 01:34:23 -05:00
var tokens = this . _super ( ) ;
2015-01-20 20:25:26 -05:00
2015-02-10 01:34:23 -05:00
try {
2015-02-24 00:33:29 -05:00
tokens = f . _remove _banned ( tokens ) ;
2015-02-10 01:34:23 -05:00
tokens = f . _emoticonize ( this , tokens ) ;
var user = f . get _user ( ) ;
if ( ! user || this . get ( "model.from" ) != user . login )
tokens = f . _mentionize ( this , tokens ) ;
} catch ( err ) {
try {
f . error ( "LineController tokenizedMessage: " + err ) ;
} catch ( err ) { }
}
2015-01-20 20:25:26 -05:00
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 ( ) ;
2015-02-10 01:34:23 -05:00
try {
var el = this . get ( 'element' ) ,
2015-02-24 00:33:29 -05:00
controller = this . get ( 'context' ) ,
user = controller . get ( 'model.from' ) ,
room = controller . get ( 'parentController.content.id' ) ,
color = controller . get ( 'model.color' ) ,
2015-01-20 01:53:18 -05:00
2015-02-24 00:33:29 -05:00
row _type = controller . get ( 'model.ffz_alternate' ) ;
2015-02-08 02:01:09 -05:00
2015-01-20 01:53:18 -05:00
2015-02-10 01:34:23 -05:00
// Color Processing
if ( color )
f . _handle _color ( color ) ;
// Row Alternation
if ( row _type === undefined ) {
row _type = f . _last _row [ room ] = f . _last _row . hasOwnProperty ( room ) ? ! f . _last _row [ room ] : false ;
this . set ( "context.model.ffz_alternate" , row _type ) ;
2015-02-08 02:01:09 -05:00
}
2015-02-10 01:34:23 -05:00
el . classList . toggle ( 'ffz-alternate' , row _type ) ;
// Basic Data
el . setAttribute ( 'data-room' , room ) ;
el . setAttribute ( 'data-sender' , user ) ;
// Badge
f . render _badge ( this ) ;
// Capitalization
if ( f . settings . capitalize )
f . capitalize ( this , user ) ;
// Mention Highlighting
var mentioned = el . querySelector ( 'span.mentioned' ) ;
if ( mentioned ) {
el . classList . add ( "ffz-mentioned" ) ;
if ( ! document . hasFocus ( ) && ! this . get ( 'context.model.ffz_notified' ) && 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.ffz_notified' , true ) ;
2015-02-24 00:33:29 -05:00
// Banned Links
var bad _links = el . querySelectorAll ( 'a.deleted-link' ) ;
for ( var i = 0 ; i < bad _links . length ; i ++ ) {
var link = bad _links [ i ] ;
link . addEventListener ( "click" , function ( e ) {
if ( ! this . classList . contains ( "deleted-link" ) )
return true ;
// Get the URL
var href = this . getAttribute ( 'data-url' ) ,
link = href ;
// Delete Old Stuff
this . classList . remove ( 'deleted-link' ) ;
this . removeAttribute ( "data-url" ) ;
this . removeAttribute ( "title" ) ;
this . removeAttribute ( "original-title" ) ;
// Process URL
if ( href . indexOf ( "@" ) > - 1 && ( - 1 === href . indexOf ( "/" ) || href . indexOf ( "@" ) < href . indexOf ( "/" ) ) )
href = "mailto:" + href ;
else if ( ! href . match ( /^https?:\/\// ) )
href = "http://" + href ;
// Set up the Link
this . href = href ;
this . target = "_new" ;
this . textContent = link ;
// Stop from Navigating
e . preventDefault ( ) ;
} ) ;
// Also add a nice tooltip.
jQuery ( link ) . tipsy ( ) ;
}
// Enhanced Emotes
var images = el . querySelectorAll ( 'img' ) ;
for ( var i = 0 ; i < images . length ; i ++ ) {
var img = images [ i ] ,
name = img . alt ,
match = /\/emoticons\/v1\/(\d+)\/1\.0/ . exec ( img . src ) ,
id = match ? parseInt ( match [ 1 ] ) : null ;
if ( id !== null ) {
// High-DPI Images
img . setAttribute ( 'srcset' , build _srcset ( id ) ) ;
img . setAttribute ( 'emote-id' , id ) ;
// Source Lookup
var emote _data = f . _twitch _emotes [ id ] ;
if ( emote _data ) {
if ( typeof emote _data != "string" )
img . title = emote _data . tooltip ;
} else {
f . _twitch _emotes [ id ] = img . alt ;
f . ws _send ( "twitch_emote" , id , load _emote _data . bind ( f , id , img . alt ) ) ;
}
jQuery ( img ) . tipsy ( { html : true } ) ;
} else if ( img . getAttribute ( 'data-ffz-emote' ) ) {
var data = JSON . parse ( decodeURIComponent ( img . getAttribute ( 'data-ffz-emote' ) ) ) ,
id = data && data [ 0 ] || null ,
set _id = data && data [ 1 ] || null ,
set = f . emote _sets [ set _id ] ,
emote = set ? set . emotes [ id ] : null ,
set _name = set . id ,
set _type = "FFZ Channel" ;
if ( set . id == "global" ) {
set _name = "FrankerFaceZ Global" ;
set _type = null ;
} else if ( set . id == "globalevent" ) {
set _name = "FrankerFaceZ Event" ;
set _type = null ;
} else if ( f . feature _friday && set . id == f . feature _friday . set )
set _name = "Feature Friday - " + f . feature _friday . channel ;
img . title = data _to _tooltip ( {
code : emote . hidden ? "???" : emote . name ,
set : set _name ,
set _type : set _type
} ) ;
jQuery ( img ) . tipsy ( { html : true } ) ;
} else
jQuery ( img ) . tipsy ( ) ;
}
2015-02-10 01:34:23 -05:00
} catch ( err ) {
try {
f . error ( "LineView didInsertElement: " + err ) ;
} catch ( err ) { }
}
2015-01-20 01:53:18 -05:00
}
} ) ;
2015-02-10 01:34:23 -05:00
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 ( ) ] ;
}
2015-02-10 01:34:23 -05:00
// ---------------------
// Fix Name Colors
// ---------------------
FFZ . prototype . _handle _color = function ( color ) {
if ( ! color || this . _colors [ color ] )
return ;
this . _colors [ color ] = true ;
// Parse the color.
var raw = parseInt ( color . substr ( 1 ) , 16 ) ,
rgb = [
( raw >> 16 ) ,
( raw >> 8 & 0x00FF ) ,
( raw & 0x0000FF )
] ,
lum = utils . get _luminance ( rgb ) ,
output = "" ,
rule = 'span[style="color:' + color + '"]' ,
matched = false ;
if ( lum > 0.3 ) {
// Color Too Bright. We need a lum of 0.3 or less.
matched = true ;
var s = 255 ,
nc = rgb ;
while ( s -- ) {
nc = utils . darken ( nc ) ;
if ( utils . get _luminance ( nc ) <= 0.3 )
break ;
}
output += '.ffz-chat-colors .ember-chat-container:not(.dark) .chat-line ' + rule + ', .ffz-chat-colors .chat-container:not(.dark) .chat-line ' + rule + ' { color: ' + utils . rgb _to _css ( nc ) + ' !important; }\n' ;
} else
output += '.ffz-chat-colors .ember-chat-container:not(.dark) .chat-line ' + rule + ', .ffz-chat-colors .chat-container:not(.dark) .chat-line ' + rule + ' { color: ' + color + ' !important; }\n' ;
if ( lum < 0.1 ) {
// Color Too Dark. We need a lum of 0.1 or more.
matched = true ;
var s = 255 ,
nc = rgb ;
while ( s -- ) {
nc = utils . brighten ( nc ) ;
if ( utils . get _luminance ( nc ) >= 0.1 )
break ;
}
output += '.ffz-chat-colors .theatre .chat-container .chat-line ' + rule + ', .ffz-chat-colors .chat-container.dark .chat-line ' + rule + ', .ffz-chat-colors .ember-chat-container.dark .chat-line ' + rule + ' { color: ' + utils . rgb _to _css ( nc ) + ' !important; }\n' ;
} else
output += '.ffz-chat-colors .theatre .chat-container .chat-line ' + rule + ', .ffz-chat-colors .chat-container.dark .chat-line ' + rule + ', .ffz-chat-colors .ember-chat-container.dark .chat-line ' + rule + ' { color: ' + color + ' !important; }\n' ;
if ( matched )
this . _fix _color _style . innerHTML += output ;
}
2015-01-20 01:53:18 -05:00
// ---------------------
// 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.
2015-02-24 00:33:29 -05:00
if ( window . BetterTTV && BetterTTV . chat && BetterTTV . chat . helpers . lookupDisplayName )
2015-02-08 02:01:09 -05:00
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 ] ;
}
2015-02-24 00:33:29 -05:00
if ( FFZ . _cap _fetching < 25 ) {
2015-01-20 01:53:18 -05:00
FFZ . _cap _fetching ++ ;
2015-02-24 00:33:29 -05:00
FFZ . get ( ) . ws _send ( "get_display_name" , name , function ( success , data ) {
var cap _name = success ? data : name ;
FFZ . capitalization [ name ] = [ cap _name , Date . now ( ) ] ;
FFZ . _cap _fetching -- ;
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 ) ) ;
2015-02-24 00:33:29 -05:00
if ( name && view )
2015-01-20 01:53:18 -05:00
view . $ ( '.from' ) . text ( name ) ;
}
2015-01-20 20:25:26 -05:00
// ---------------------
// Extra Mentions
// ---------------------
FFZ . _regex _cache = { } ;
2015-02-10 01:34:23 -05:00
FFZ . _get _regex = function ( word ) {
2015-02-08 02:01:09 -05:00
return FFZ . _regex _cache [ word ] = FFZ . _regex _cache [ word ] || RegExp ( "\\b" + reg _escape ( word ) + "\\b" , "ig" ) ;
}
2015-02-24 00:33:29 -05:00
FFZ . _words _to _regex = function ( list ) {
var regex = FFZ . _regex _cache [ list ] ;
if ( ! regex ) {
var reg = "" ;
for ( var i = 0 ; i < list . length ; i ++ ) {
if ( ! list [ i ] )
continue ;
reg += ( reg ? "|" : "" ) + reg _escape ( list [ i ] ) ;
}
regex = FFZ . _regex _cache [ list ] = new RegExp ( "(^|.*?" + SEPARATORS + ")(" + reg + ")(?=$|" + SEPARATORS + ")" , "ig" ) ;
}
return regex ;
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-24 00:33:29 -05:00
var regex = FFZ . _words _to _regex ( mention _words ) ,
new _tokens = [ ] ;
for ( var i = 0 ; i < tokens . length ; i ++ ) {
var token = tokens [ i ] ;
if ( ! _ . isString ( token ) ) {
new _tokens . push ( token ) ;
continue ;
}
if ( ! token . match ( regex ) ) {
new _tokens . push ( token ) ;
continue ;
}
token = token . replace ( regex , function ( all , prefix , match ) {
new _tokens . push ( prefix ) ;
new _tokens . push ( {
mentionedUser : match ,
own : false
} ) ;
return "" ;
} ) ;
if ( token )
new _tokens . push ( token ) ;
}
return new _tokens ;
}
// ---------------------
// Banned Words
// ---------------------
FFZ . prototype . _remove _banned = function ( tokens ) {
var banned _words = this . settings . banned _words ;
if ( ! banned _words || ! banned _words . length )
return tokens ;
if ( typeof tokens == "string" )
tokens = [ tokens ] ;
var regex = FFZ . _words _to _regex ( banned _words ) ,
new _tokens = [ ] ;
for ( var i = 0 ; i < tokens . length ; i ++ ) {
var token = tokens [ i ] ;
if ( ! _ . isString ( token ) ) {
if ( token . emoticonSrc && regex . test ( token . altText ) )
new _tokens . push ( token . altText . replace ( regex , "$1***" ) ) ;
else if ( token . isLink && regex . test ( token . href ) )
new _tokens . push ( {
mentionedUser : '</span><a class="deleted-link" title="' + quote _attr ( token . href . replace ( regex , "$1***" ) ) + '" data-url="' + quote _attr ( token . href ) + '" href="#"><banned link></a><span class="mentioning">' ,
own : true
} ) ;
else
new _tokens . push ( token ) ;
2015-01-20 20:25:26 -05:00
2015-02-24 00:33:29 -05:00
} else
new _tokens . push ( token . replace ( regex , "$1***" ) ) ;
}
2015-01-20 20:25:26 -05:00
2015-02-24 00:33:29 -05:00
return new _tokens ;
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};
2015-02-24 00:33:29 -05:00
var eo = { isEmoticon : true , cls : emote . klass , emoticonSrc : emote . url + '" data-ffz-emote="' + encodeURIComponent ( JSON . stringify ( [ emote . id , emote . set _id ] ) ) , altText : ( emote . hidden ? "???" : emote . name ) } ;
2015-01-20 01:53:18 -05:00
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-02-24 00:33:29 -05:00
} , { "../utils" : 26 } ] , 7 : [ function ( require , module , exports ) {
2015-02-10 01:34:23 -05:00
var FFZ = window . FrankerFaceZ ,
utils = require ( "../utils" ) ,
keycodes = {
ESC : 27 ,
P : 80 ,
B : 66 ,
T : 84
} ,
btns = [
[ '5m' , 300 ] ,
[ '10m' , 600 ] ,
[ '1hr' , 3600 ] ,
[ '12hr' , 43200 ] ,
[ '24hr' , 86400 ] ] ,
2015-02-10 02:42:11 -05:00
MESSAGE = '<svg class="svg-messages" height="16px" version="1.1" viewBox="0 0 18 18" width="16px" x="0px" y="0px"><path clip-rule="evenodd" d="M1,15V3h16v12H1z M15.354,5.354l-0.707-0.707L9,10.293L3.354,4.646L2.646,5.354L6.293,9l-3.646,3.646l0.707,0.707L7,9.707l1.646,1.646h0.707L11,9.707l3.646,3.646l0.707-0.707L11.707,9L15.354,5.354z" fill-rule="evenodd"></path></svg>' ,
CHECK = '<svg class="svg-unban" height="16px" version="1.1" viewBox="0 0 16 16" width="16px" x="0px" y="0px"><path fill-rule="evenodd" clip-rule="evenodd" fill="#888888" d="M6.5,12.75L2,8.25l2-2l2.5,2.5l5.5-5.5l2,2L6.5,12.75z"/></svg>' ;
2015-02-10 01:34:23 -05:00
// ----------------
// Settings
// ----------------
FFZ . settings _info . enhanced _moderation = {
type : "boolean" ,
value : false ,
visible : function ( ) { return ! this . has _bttv } ,
category : "Chat" ,
name : "Enhanced Moderation" ,
2015-02-24 00:33:29 -05:00
help : "Use /p, /t, /u and /b in chat to moderate chat, or use hotkeys with moderation cards."
2015-02-10 01:34:23 -05:00
} ;
// ----------------
// Initialization
// ----------------
FFZ . prototype . setup _mod _card = function ( ) {
this . log ( "Hooking the Ember Moderation Card view." ) ;
var Card = App . _ _container _ _ . resolve ( 'view:moderation-card' ) ,
f = this ;
Card . reopen ( {
didInsertElement : function ( ) {
this . _super ( ) ;
try {
2015-02-10 02:42:11 -05:00
if ( f . has _bttv || ! f . settings . enhanced _moderation )
2015-02-10 01:34:23 -05:00
return ;
var el = this . get ( 'element' ) ,
controller = this . get ( 'context' ) ;
2015-02-10 02:42:11 -05:00
// Style it!
el . classList . add ( 'ffz-moderation-card' ) ;
2015-02-10 01:34:23 -05:00
// Only do the big stuff if we're mod.
if ( controller . get ( 'parentController.model.isModeratorOrHigher' ) ) {
2015-02-10 02:42:11 -05:00
el . classList . add ( 'ffz-is-mod' ) ;
2015-02-10 01:34:23 -05:00
el . setAttribute ( 'tabindex' , 1 ) ;
// Key Handling
el . addEventListener ( 'keyup' , function ( e ) {
var key = e . keyCode || e . which ,
user _id = controller . get ( 'model.user.id' ) ,
room = controller . get ( 'parentController.model' ) ;
if ( key == keycodes . P )
room . send ( "/timeout " + user _id + " 1" ) ;
else if ( key == keycodes . B )
room . send ( "/ban " + user _id ) ;
else if ( key == keycodes . T )
room . send ( "/timeout " + user _id + " 600" ) ;
else if ( key != keycodes . ESC )
return ;
controller . send ( 'hideModOverlay' ) ;
} ) ;
// Extra Moderation
var line = document . createElement ( 'div' ) ;
line . className = 'interface clearfix' ;
var btn _click = function ( timeout ) {
var user _id = controller . get ( 'model.user.id' ) ,
room = controller . get ( 'parentController.model' ) ;
if ( timeout === - 1 )
room . send ( "/unban " + user _id ) ;
else
room . send ( "/timeout " + user _id + " " + timeout ) ;
} ,
btn _make = function ( text , timeout ) {
var btn = document . createElement ( 'button' ) ;
btn . className = 'button' ;
btn . innerHTML = text ;
btn . title = "Timeout User for " + utils . number _commas ( timeout ) + " Second" + ( timeout != 1 ? "s" : "" ) ;
if ( timeout === 600 )
btn . title = "(T)" + btn . title . substr ( 1 ) ;
else if ( timeout === 1 )
btn . title = "(P)urge - " + btn . title ;
jQuery ( btn ) . tipsy ( ) ;
btn . addEventListener ( 'click' , btn _click . bind ( this , timeout ) ) ;
return btn ;
} ;
line . appendChild ( btn _make ( 'Purge' , 1 ) ) ;
var s = document . createElement ( 'span' ) ;
s . className = 'right' ;
line . appendChild ( s ) ;
for ( var i = 0 ; i < btns . length ; i ++ )
s . appendChild ( btn _make ( btns [ i ] [ 0 ] , btns [ i ] [ 1 ] ) ) ;
el . appendChild ( line ) ;
// Unban Button
var unban _btn = document . createElement ( 'button' ) ;
unban _btn . className = 'unban button glyph-only light' ;
2015-02-10 02:42:11 -05:00
unban _btn . innerHTML = CHECK ;
2015-02-10 01:34:23 -05:00
unban _btn . title = "(U)nban User" ;
jQuery ( unban _btn ) . tipsy ( ) ;
unban _btn . addEventListener ( "click" , btn _click . bind ( this , - 1 ) ) ;
var ban _btn = el . querySelector ( 'button.ban' ) ;
ban _btn . setAttribute ( 'title' , '(B)an User' ) ;
jQuery ( ban _btn ) . after ( unban _btn ) ;
// Fix Other Buttons
this . $ ( "button.timeout" ) . remove ( ) ;
}
// More Fixing Other Buttons
var op _btn = el . querySelector ( 'button.mod' ) ;
if ( op _btn ) {
var model = controller . get ( 'parentController.model' ) ,
can _op = model . get ( 'isBroadcaster' ) || model . get ( 'isStaff' ) || model . get ( 'isAdmin' ) ;
if ( ! can _op )
op _btn . parentElement . removeChild ( op _btn ) ;
}
var msg _btn = el . querySelector ( ".interface > button" ) ;
if ( msg _btn && msg _btn . className == "button" ) {
msg _btn . innerHTML = MESSAGE ;
msg _btn . classList . add ( 'glyph-only' ) ;
msg _btn . classList . add ( 'message' ) ;
msg _btn . title = "Message User" ;
jQuery ( msg _btn ) . tipsy ( ) ;
}
// Focus the Element
this . $ ( ) . draggable ( {
start : function ( ) {
el . focus ( ) ;
} } ) ;
el . focus ( ) ;
} catch ( err ) {
try {
f . error ( "ModerationCardView didInsertElement: " + err ) ;
} catch ( err ) { }
}
} } ) ;
}
// ----------------
// Chat Commands
// ----------------
FFZ . chat _commands . purge = FFZ . chat _commands . p = function ( room , args ) {
if ( ! args || ! args . length )
return "Purge Usage: /p username [more usernames separated by spaces]" ;
if ( args . length > 10 )
return "Please only purge up to 10 users at once." ;
for ( var i = 0 ; i < args . length ; i ++ ) {
var name = args [ i ] ;
if ( name )
room . room . send ( "/timeout " + name + " 1" ) ;
}
}
FFZ . chat _commands . p . enabled = function ( ) { return this . settings . enhanced _moderation ; }
FFZ . chat _commands . t = function ( room , args ) {
if ( ! args || ! args . length )
return "Timeout Usage: /t username [duration]" ;
room . room . send ( "/timeout " + args . join ( " " ) ) ;
}
FFZ . chat _commands . t . enabled = function ( ) { return this . settings . enhanced _moderation ; }
FFZ . chat _commands . b = function ( room , args ) {
if ( ! args || ! args . length )
return "Ban Usage: /b username [more usernames separated by spaces]" ;
if ( args . length > 10 )
return "Please only ban up to 10 users at once." ;
for ( var i = 0 ; i < args . length ; i ++ ) {
var name = args [ i ] ;
if ( name )
room . room . send ( "/ban " + name ) ;
}
}
FFZ . chat _commands . b . enabled = function ( ) { return this . settings . enhanced _moderation ; }
FFZ . chat _commands . u = function ( room , args ) {
if ( ! args || ! args . length )
2015-02-24 00:33:29 -05:00
return "Unban Usage: /u username [more usernames separated by spaces]" ;
2015-02-10 01:34:23 -05:00
if ( args . length > 10 )
return "Please only unban up to 10 users at once." ;
for ( var i = 0 ; i < args . length ; i ++ ) {
var name = args [ i ] ;
if ( name )
room . room . send ( "/unban " + name ) ;
}
}
FFZ . chat _commands . u . enabled = function ( ) { return this . settings . enhanced _moderation ; }
2015-02-24 00:33:29 -05:00
} , { "../utils" : 26 } ] , 8 : [ 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 = { } ;
2015-02-10 01:34:23 -05:00
FFZ . ffz _commands = { } ;
2015-01-20 01:53:18 -05:00
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 ) {
2015-02-10 01:34:23 -05:00
var room = this . rooms [ room _id ] ;
if ( ! room || ! room . room )
return false ;
if ( ! text )
return ;
var args = text . split ( " " ) ,
cmd = args . shift ( ) . substr ( 1 ) . toLowerCase ( ) ,
command = FFZ . chat _commands [ cmd ] ,
output ;
if ( ! command )
return false ;
if ( command . hasOwnProperty ( 'enabled' ) ) {
var val = command . enabled ;
if ( typeof val == "function" ) {
try {
val = command . enabled . bind ( this ) ( room , args ) ;
} catch ( err ) {
this . error ( 'command "' + cmd + '" enabled: ' + err ) ;
val = false ;
}
}
if ( ! val )
return false ;
}
this . log ( "Received Command: " + cmd , args , true ) ;
try {
output = command . bind ( this ) ( room , args ) ;
} catch ( err ) {
this . error ( 'command "' + cmd + '" runner: ' + err ) ;
output = "There was an error running the command." ;
}
if ( output )
this . room _message ( room , output ) ;
return true ;
}
FFZ . prototype . run _ffz _command = function ( text , room _id ) {
2015-01-20 01:53:18 -05:00
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 ) ;
2015-02-10 01:34:23 -05:00
var command = FFZ . ffz _commands [ cmd ] , output ;
2015-01-20 01:53:18 -05:00
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 ) ;
}
2015-02-10 01:34:23 -05:00
FFZ . ffz _commands . help = function ( room , args ) {
2015-01-20 01:53:18 -05:00
if ( args && args . length ) {
2015-02-10 01:34:23 -05:00
var command = FFZ . ffz _commands [ args [ 0 ] . toLowerCase ( ) ] ;
2015-01-20 01:53:18 -05:00
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 = [ ] ;
2015-02-10 01:34:23 -05:00
for ( var c in FFZ . ffz _commands )
FFZ . ffz _commands . hasOwnProperty ( c ) && cmds . push ( c ) ;
2015-01-20 01:53:18 -05:00
return "The available commands are: " + cmds . join ( ", " ) ;
}
2015-02-10 01:34:23 -05:00
FFZ . ffz _commands . help . help = "Usage: /ffz help [command]\nList available commands, or show help for a specific command." ;
2015-01-20 01:53:18 -05:00
// --------------------
// 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 ( ) ;
2015-02-10 01:34:23 -05:00
try {
f . add _room ( this . id , this ) ;
} catch ( err ) {
f . error ( "add_room: " + err ) ;
}
2015-01-20 01:53:18 -05:00
} ,
willDestroy : function ( ) {
this . _super ( ) ;
2015-02-10 01:34:23 -05:00
try {
f . remove _room ( this . id ) ;
} catch ( err ) {
f . error ( "remove_room: " + err ) ;
}
2015-01-20 01:53:18 -05:00
} ,
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-10 01:34:23 -05:00
try {
if ( f . settings . capitalize )
suggestions = _ . map ( suggestions , FFZ . get _capitalization ) ;
} catch ( err ) {
f . error ( "get_suggestions: " + err ) ;
}
2015-01-20 20:25:26 -05:00
return suggestions ;
} ,
2015-01-20 01:53:18 -05:00
send : function ( text ) {
2015-02-10 01:34:23 -05:00
try {
var cmd = text . split ( ' ' , 1 ) [ 0 ] . toLowerCase ( ) ;
if ( cmd === "/ffz" ) {
this . set ( "messageToSend" , "" ) ;
f . run _ffz _command ( text . substr ( 5 ) , this . get ( 'id' ) ) ;
return ;
} else if ( cmd . charAt ( 0 ) === "/" && f . run _command ( text , this . get ( 'id' ) ) ) {
this . set ( "messageToSend" , "" ) ;
return ;
}
} catch ( err ) {
f . error ( "send: " + err ) ;
}
return this . _super ( text ) ;
2015-01-20 01:53:18 -05:00
}
} ) ;
}
// --------------------
// 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-24 00:33:29 -05:00
} , { "../constants" : 3 , "../utils" : 26 } ] , 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 ( ) {
2015-02-10 01:34:23 -05:00
var viewers = this . _super ( ) ;
try {
var 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 ( ) ] ;
}
2015-01-20 01:53:18 -05:00
2015-02-10 01:34:23 -05:00
// 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 ] = [ ] ;
} 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 ) ;
}
2015-01-20 01:53:18 -05:00
}
2015-02-10 01:34:23 -05:00
// 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 ] ;
2015-01-20 01:53:18 -05:00
2015-02-10 01:34:23 -05:00
if ( ! chatters || ! chatters . length )
continue ;
2015-01-20 01:53:18 -05:00
2015-02-10 01:34:23 -05:00
viewers . push ( { category : category } ) ;
viewers . push ( { chatter : "" } ) ;
2015-01-20 01:53:18 -05:00
2015-02-10 01:34:23 -05:00
// Push the chatters, capitalizing them as we go.
chatters . sort ( ) ;
while ( chatters . length ) {
var viewer = chatters . shift ( ) ;
viewer = FFZ . get _capitalization ( viewer ) ;
viewers . push ( { chatter : viewer } ) ;
}
2015-01-20 01:53:18 -05:00
}
2015-02-10 01:34:23 -05:00
} catch ( err ) {
f . error ( "ViewersController lines: " + err ) ;
2015-01-20 01:53:18 -05:00
}
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 ,
2015-02-24 00:33:29 -05:00
emote = { id : id , set _id : set _id , hidden : hidden , name : name , height : height , width : width , url : path , margins : margins , extra _css : extra } ;
2015-01-20 01:53:18 -05:00
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-24 00:33:29 -05:00
} , { "./constants" : 3 , "./utils" : 26 } ] , 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 ;
2015-02-10 01:34:23 -05:00
// this.track('setCustomVariable', '3', 'BetterTTV', BetterTTV.info.versionString());
2015-01-19 15:27:10 -05:00
2015-02-08 02:14:52 -05:00
// Disable Dark if it's enabled.
2015-02-10 01:34:23 -05:00
document . body . classList . remove ( "ffz-dark" ) ;
2015-02-08 02:14:52 -05:00
if ( this . _dark _style ) {
this . _dark _style . parentElement . removeChild ( this . _dark _style ) ;
delete this . _dark _style ;
}
2015-02-10 01:34:23 -05:00
// Disable other features too.
document . body . classList . remove ( "ffz-chat-colors" ) ;
document . body . classList . remove ( "ffz-chat-background" ) ;
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" )
2015-02-10 01:34:23 -05:00
f . run _ffz _command ( message . substr ( 5 ) , BetterTTV . chat . store . currentRoom ) ;
2015-01-19 15:27:10 -05:00
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 ;
2015-02-10 01:34:23 -05:00
// Logging
this . _log _data = [ ] ;
2015-01-20 01:53:18 -05:00
// Get things started.
this . initialize ( ) ;
}
FFZ . get = function ( ) { return FFZ . instance ; }
// Version
var VER = FFZ . version _info = {
2015-02-24 00:33:29 -05:00
major : 3 , minor : 2 , revision : 1 ,
2015-01-20 01:53:18 -05:00
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 ) : "" ) ;
2015-02-10 01:34:23 -05:00
this . _log _data . push ( msg ) ;
2015-01-20 01:53:18 -05:00
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 ) ;
}
2015-02-10 01:34:23 -05:00
FFZ . prototype . error = function ( msg , data , to _json ) {
msg = "FFZ Error: " + msg + ( to _json ? " -- " + JSON . stringify ( data ) : "" ) ;
this . _log _data . push ( msg ) ;
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 . assert ( false , msg ) ;
}
FFZ . prototype . paste _logs = function ( ) {
this . _pastebin ( this . _log _data . join ( "\n" ) , function ( url ) {
if ( ! url )
return console . log ( "FFZ Error: Unable to upload log to pastebin." ) ;
console . log ( "FFZ: Your FrankerFaceZ log has been pasted to: " + url ) ;
} ) ;
}
FFZ . prototype . _pastebin = function ( data , callback ) {
jQuery . ajax ( { url : "http://putco.de/" , type : "PUT" , data : data , context : this } )
. success ( function ( e ) {
callback . bind ( this ) ( e . trim ( ) + ".log" ) ;
} ) . fail ( function ( e ) {
callback . bind ( this ) ( null ) ;
} ) ;
}
2015-01-20 01:53:18 -05:00
// -------------------
// 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');
2015-02-24 00:33:29 -05:00
// Import these first to set up data structures
require ( './ui/menu' ) ;
2015-02-08 02:01:09 -05:00
require ( './settings' ) ;
2015-01-20 01:53:18 -05:00
require ( './socket' ) ;
2015-02-24 00:33:29 -05:00
2015-01-20 01:53:18 -05:00
require ( './emoticons' ) ;
require ( './badges' ) ;
2015-02-10 01:34:23 -05:00
// Analytics: require('./ember/router');
2015-01-20 01:53:18 -05:00
require ( './ember/room' ) ;
require ( './ember/line' ) ;
require ( './ember/chatview' ) ;
require ( './ember/viewers' ) ;
2015-02-10 01:34:23 -05:00
require ( './ember/moderation-card' ) ;
2015-01-20 01:53:18 -05:00
//require('./ember/teams');
2015-02-10 01:34:23 -05:00
// Analytics: require('./tracking');
2015-01-20 01:53:18 -05:00
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' ) ;
2015-02-08 02:01:09 -05:00
require ( './ui/races' ) ;
2015-02-24 00:33:29 -05:00
require ( './ui/my_emotes' ) ;
require ( './ui/about_page' ) ;
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 ( ) ;
2015-02-10 01:34:23 -05:00
//this.setup_piwik();
2015-01-20 01:53:18 -05:00
2015-02-10 01:34:23 -05:00
//this.setup_router();
2015-01-20 01:53:18 -05:00
this . setup _room ( ) ;
this . setup _line ( ) ;
this . setup _chatview ( ) ;
this . setup _viewers ( ) ;
2015-02-10 01:34:23 -05:00
this . setup _mod _card ( ) ;
2015-01-20 01:53:18 -05:00
//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-24 00:33:29 -05:00
this . setup _my _emotes ( ) ;
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-24 00:33:29 -05:00
} , { "./badges" : 1 , "./commands" : 2 , "./debug" : 4 , "./ember/chatview" : 5 , "./ember/line" : 6 , "./ember/moderation-card" : 7 , "./ember/room" : 8 , "./ember/viewers" : 9 , "./emoticons" : 10 , "./ext/betterttv" : 11 , "./ext/emote_menu" : 12 , "./featurefriday" : 14 , "./settings" : 15 , "./shims" : 16 , "./socket" : 17 , "./ui/about_page" : 18 , "./ui/menu" : 19 , "./ui/menu_button" : 20 , "./ui/my_emotes" : 21 , "./ui/notifications" : 22 , "./ui/races" : 23 , "./ui/styles" : 24 , "./ui/viewer_count" : 25 } ] , 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.
2015-02-10 01:34:23 -05:00
// btn.addEventListener('click', function() { f.track('trackLink', this.href, 'link'); });
2015-01-16 17:45:37 -05:00
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 ,
2015-02-24 00:33:29 -05:00
constants = require ( "./constants" ) ;
2015-02-08 02:01:09 -05:00
make _ls = function ( key ) {
return "ffz_setting_" + key ;
2015-02-24 00:33:29 -05:00
} ,
toggle _setting = function ( swit , key ) {
var val = ! this . settings . get ( key ) ;
this . settings . set ( key , val ) ;
swit . classList . toggle ( 'active' , val ) ;
2015-02-08 02:01:09 -05:00
} ;
// --------------------
// 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 ) {
2015-02-24 00:33:29 -05:00
if ( ! FFZ . settings _info . hasOwnProperty ( key ) )
continue ;
var info = FFZ . settings _info [ key ] ,
ls _key = info . storage _key || make _ls ( key ) ,
2015-02-08 02:01:09 -05:00
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
2015-02-24 00:33:29 -05:00
window . addEventListener ( "storage" , this . _setting _update . bind ( this ) , false ) ;
2015-02-08 02:01:09 -05:00
}
// --------------------
2015-02-24 00:33:29 -05:00
// Menu Page
2015-02-08 02:01:09 -05:00
// --------------------
2015-02-24 00:33:29 -05:00
FFZ . menu _pages . settings = {
render : function ( view , container ) {
var settings = { } ,
categories = [ ] ;
for ( var key in FFZ . settings _info ) {
if ( ! FFZ . settings _info . hasOwnProperty ( key ) )
continue ;
2015-02-08 02:01:09 -05:00
2015-02-24 00:33:29 -05:00
var info = FFZ . settings _info [ key ] ,
cat = info . category || "Miscellaneous" ,
cs = settings [ cat ] ;
2015-02-08 02:01:09 -05:00
2015-02-24 00:33:29 -05:00
if ( info . visible !== undefined && info . visible !== null ) {
var visible = info . visible ;
if ( typeof info . visible == "function" )
visible = info . visible . bind ( this ) ( ) ;
2015-02-08 02:01:09 -05:00
2015-02-24 00:33:29 -05:00
if ( ! visible )
continue ;
}
2015-02-08 02:01:09 -05:00
2015-02-24 00:33:29 -05:00
if ( ! cs ) {
categories . push ( cat ) ;
cs = settings [ cat ] = [ ] ;
}
2015-02-08 02:01:09 -05:00
2015-02-24 00:33:29 -05:00
cs . push ( [ key , info ] ) ;
}
2015-02-08 02:01:09 -05:00
2015-02-24 00:33:29 -05:00
categories . sort ( function ( a , b ) {
var a = a . toLowerCase ( ) ,
b = b . toLowerCase ( ) ;
2015-02-08 02:01:09 -05:00
2015-02-24 00:33:29 -05:00
if ( a === "Debugging" )
a = "zzz" + a ;
2015-02-08 02:01:09 -05:00
2015-02-24 00:33:29 -05:00
if ( b === "Debugging" )
b = "zzz" + b ;
2015-02-08 02:01:09 -05:00
2015-02-24 00:33:29 -05:00
if ( a < b ) return - 1 ;
else if ( a > b ) return 1 ;
return 0 ;
} ) ;
2015-02-08 02:01:09 -05:00
2015-02-24 00:33:29 -05:00
for ( var ci = 0 ; ci < categories . length ; ci ++ ) {
var category = categories [ ci ] ,
cset = settings [ category ] ,
2015-02-08 02:01:09 -05:00
2015-02-24 00:33:29 -05:00
menu = document . createElement ( 'div' ) ,
heading = document . createElement ( 'div' ) ;
2015-02-08 02:01:09 -05:00
2015-02-24 00:33:29 -05:00
heading . className = 'heading' ;
menu . className = 'chat-menu-content' ;
heading . innerHTML = category ;
menu . appendChild ( heading ) ;
2015-02-08 02:01:09 -05:00
2015-02-24 00:33:29 -05:00
cset . sort ( function ( a , b ) {
var a = a [ 1 ] ,
b = b [ 1 ] ,
2015-02-08 02:01:09 -05:00
2015-02-24 00:33:29 -05:00
at = a . type ,
bt = b . type ,
2015-02-08 02:01:09 -05:00
2015-02-24 00:33:29 -05:00
an = a . name . toLowerCase ( ) ,
bn = b . name . toLowerCase ( ) ;
2015-02-08 02:01:09 -05:00
2015-02-24 00:33:29 -05:00
if ( at < bt ) return - 1 ;
else if ( at > bt ) return 1 ;
2015-02-08 02:01:09 -05:00
2015-02-24 00:33:29 -05:00
else if ( an < bn ) return - 1 ;
else if ( an > bn ) return 1 ;
2015-02-08 02:01:09 -05:00
2015-02-24 00:33:29 -05:00
return 0 ;
} ) ;
2015-02-08 02:01:09 -05:00
2015-02-24 00:33:29 -05:00
for ( var i = 0 ; i < cset . length ; i ++ ) {
var key = cset [ i ] [ 0 ] ,
info = cset [ i ] [ 1 ] ,
el = document . createElement ( 'p' ) ,
val = this . settings . get ( key ) ;
2015-02-08 02:01:09 -05:00
2015-02-24 00:33:29 -05:00
el . className = 'clearfix' ;
2015-02-08 02:01:09 -05:00
2015-02-24 00:33:29 -05:00
if ( info . type == "boolean" ) {
var swit = document . createElement ( 'a' ) ,
label = document . createElement ( 'span' ) ;
2015-01-20 01:53:18 -05:00
2015-02-24 00:33:29 -05:00
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" , 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 ,
sort _order : 99999
} ;
// --------------------
// 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 ] ;
if ( ! info ) {
// Try iterating to find the key.
for ( key in FFZ . settings _info ) {
if ( ! FFZ . settings _info . hasOwnProperty ( key ) )
continue ;
info = FFZ . settings _info [ key ] ;
if ( info . storage _key == ls _key )
break ;
}
// Not us.
if ( info . storage _key != ls _key )
return ;
}
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 info = FFZ . settings _info [ key ] ,
ls _key = info . storage _key || make _ls ( 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 info = FFZ . settings _info [ key ] ,
ls _key = info . storage _key || make _ls ( 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 ) ;
}
}
} , { "./constants" : 3 } ] , 16 : [ function ( require , module , exports ) {
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 ;
2015-01-20 01:53:18 -05:00
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 )
2015-02-24 00:33:29 -05:00
f . rooms . hasOwnProperty ( room _id ) && f . ws _send ( "sub" , room _id ) ;
2015-01-20 01:53:18 -05:00
// 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.
2015-02-24 00:33:29 -05:00
if ( f . _ws _delay < 60000 )
2015-01-20 01:53:18 -05:00
f . _ws _delay += 5000 ;
2015-02-24 00:33:29 -05:00
else
// Randomize delay.
f . _ws _delay = ( Math . floor ( Math . random ( ) * 60 ) + 30 ) * 1000 ;
2015-01-20 01:53:18 -05:00
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-24 00:33:29 -05:00
// ----------------
// Authorization
// ----------------
FFZ . ws _commands . do _authorize = function ( data ) {
// Try finding a channel we can send on.
var conn ;
for ( var room _id in this . rooms ) {
if ( ! this . rooms . hasOwnProperty ( room _id ) )
continue ;
var r = this . rooms [ room _id ] ;
if ( r && r . room && ! r . room . get ( 'roomProperties.eventchat' ) && ! r . room . get ( 'isGroupRoom' ) && r . room . tmiRoom ) {
var c = r . room . tmiRoom . _getConnection ( ) ;
if ( c . isConnected ) {
conn = c ;
break ;
}
}
}
if ( conn )
conn . _send ( "PRIVMSG #frankerfacezauthorizer :AUTH " + data ) ;
else
// Try again shortly.
setTimeout ( FFZ . ws _commands . do _authorize . bind ( this , data ) , 5000 ) ;
}
2015-02-08 02:01:09 -05:00
} , { } ] , 18 : [ function ( require , module , exports ) {
2015-02-24 00:33:29 -05:00
var FFZ = window . FrankerFaceZ ,
constants = require ( "../constants" ) ;
// -------------------
// About Page
// -------------------
FFZ . menu _pages . about = {
name : "About FrankerFaceZ" ,
icon : constants . HEART ,
sort _order : 998 ,
render : function ( view , container ) {
var room = this . rooms [ view . get ( "context.currentRoom.id" ) ] ,
has _emotes = false , f = this ;
// 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 ;
}
}
}
// Heading
var heading = document . createElement ( 'div' ) ,
content = '' ;
content += "<h1>FrankerFaceZ</h1>" ;
content += '<div class="ffz-about-subheading">new ways to woof</div>' ;
heading . className = 'chat-menu-content center' ;
heading . innerHTML = content ;
container . appendChild ( heading ) ;
// Advertising
var btn _container = document . createElement ( 'div' ) ,
ad _button = document . createElement ( 'a' ) ,
message = "To use custom emoticons in " + ( has _emotes ? "this channel" : "tons of channels" ) + ", get FrankerFaceZ from http://www.frankerfacez.com" ;
ad _button . className = 'button primary' ;
ad _button . innerHTML = "Advertise in Chat" ;
ad _button . addEventListener ( 'click' , this . _add _emote . bind ( this , view , message ) ) ;
btn _container . appendChild ( ad _button ) ;
// Donate
var donate _button = document . createElement ( 'a' ) ;
donate _button . className = 'button ffz-donate' ;
donate _button . href = "http://www.frankerfacez.com/donate.html" ;
donate _button . target = "_new" ;
donate _button . innerHTML = "Donate" ;
btn _container . appendChild ( donate _button ) ;
btn _container . className = 'chat-menu-content center' ;
container . appendChild ( btn _container ) ;
// Credits
var credits = document . createElement ( 'div' ) ;
content = '<table class="ffz-about-table">' ;
content += '<tr><th colspan="4">Developers</th></tr>' ;
content += '<tr><td>Dan Salvato</td><td><a class="twitch" href="http://www.twitch.tv/dansalvato" title="Twitch" target="_new"> </a></td><td><a class="twitter" href="https://twitter.com/dansalvato1" title="Twitter" target="_new"> </a></td><td><a class="youtube" href="https://www.youtube.com/user/dansalvato1" title="YouTube" target="_new"> </a></td></tr>' ;
content += '<tr><td>Stendec</td><td><a class="twitch" href="http://www.twitch.tv/sirstendec" title="Twitch" target="_new"> </a></td><td><a class="twitter" href="https://twitter.com/SirStendec" title="Twitter" target="_new"> </a></td><td><a class="youtube" href="https://www.youtube.com/channel/UCnxuvmK1DCPCXSJ-mXIh4KQ" title="YouTube" target="_new"> </a></td></tr>' ;
content += '<tr class="debug"><td>Version ' + FFZ . version _info + '</td><td colspan="3"><a href="#" id="ffz-debug-logs">Logs</a></td></tr>' ;
credits . className = 'chat-menu-content center' ;
credits . innerHTML = content ;
// Make the Logs button functional.
var getting _logs = false ;
credits . querySelector ( '#ffz-debug-logs' ) . addEventListener ( 'click' , function ( ) {
if ( getting _logs )
return ;
getting _logs = true ;
f . _pastebin ( f . _log _data . join ( "\n" ) , function ( url ) {
getting _logs = false ;
if ( ! url )
alert ( "There was an error uploading the FrankerFaceZ logs." ) ;
else
prompt ( "Your FrankerFaceZ logs have been uploaded to the URL:" , url ) ;
} ) ;
} ) ;
container . appendChild ( credits ) ;
}
}
} , { "../constants" : 3 } ] , 19 : [ function ( require , module , exports ) {
2015-02-08 02:01:09 -05:00
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-24 00:33:29 -05:00
var heading = document . createElement ( 'li' ) ;
heading . className = 'title' ;
heading . innerHTML = "<span>" + ( constants . DEBUG ? "[DEV] " : "" ) + "FrankerFaceZ</span>" ;
menu . appendChild ( heading ) ;
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-24 00:33:29 -05:00
var menu _pages = [ ] ;
2015-02-08 02:01:09 -05:00
for ( var key in FFZ . menu _pages ) {
2015-02-24 00:33:29 -05:00
if ( ! FFZ . menu _pages . hasOwnProperty ( key ) )
continue ;
2015-02-08 02:01:09 -05:00
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-24 00:33:29 -05:00
menu _pages . push ( [ page . sort _order || 0 , key , page ] ) ;
}
menu _pages . sort ( function ( a , b ) {
if ( a [ 0 ] < b [ 0 ] ) return 1 ;
else if ( a [ 0 ] > b [ 0 ] ) return - 1 ;
var al = a [ 1 ] . toLowerCase ( ) ,
bl = b [ 1 ] . toLowerCase ( ) ;
if ( al < bl ) return 1 ;
if ( al > bl ) return - 1 ;
return 0 ;
} ) ;
for ( var i = 0 ; i < menu _pages . length ; i ++ ) {
var key = menu _pages [ i ] [ 1 ] ,
page = menu _pages [ i ] [ 2 ] ,
el = document . createElement ( 'li' ) ,
2015-02-08 02:01:09 -05:00
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 ;
2015-02-24 00:33:29 -05:00
jQuery ( link ) . tipsy ( ) ;
2015-02-08 02:01:09 -05:00
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-10 01:34:23 -05:00
sub _container . style . maxHeight = Math . max ( 100 , view . $ ( ) . height ( ) - 162 ) + "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 = "" ;
2015-02-24 00:33:29 -05:00
container . setAttribute ( 'data-page' , page ) ;
2015-02-08 02:01:09 -05:00
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 ) ;
}
// --------------------
2015-02-24 00:33:29 -05:00
// Favorites Page
2015-02-08 02:01:09 -05:00
// --------------------
2015-02-24 00:33:29 -05:00
FFZ . prototype . _tokenize _message = function ( message , room _id ) {
var lc = App . _ _container _ _ . lookup ( 'controller:line' ) ,
rc = App . _ _container _ _ . lookup ( 'controller:room' ) ,
room = this . rooms [ room _id ] ,
user = this . get _user ( ) ;
2015-02-10 01:34:23 -05:00
2015-02-24 00:33:29 -05:00
if ( ! lc || ! rc || ! room )
return [ message ] ;
2015-02-08 02:01:09 -05:00
2015-02-24 00:33:29 -05:00
rc . set ( 'model' , room . room ) ;
lc . set ( 'parentController' , rc ) ;
2015-02-08 02:01:09 -05:00
2015-02-24 00:33:29 -05:00
var model = {
from : user && user . login || "FrankerFaceZ" ,
message : message ,
tags : {
emotes : room . room . tmiSession . _emotesParser . parseEmotesTag ( message )
2015-02-10 01:34:23 -05:00
}
2015-02-24 00:33:29 -05:00
} ;
2015-02-08 02:01:09 -05:00
2015-02-24 00:33:29 -05:00
lc . set ( 'model' , model ) ;
2015-02-08 02:01:09 -05:00
2015-02-24 00:33:29 -05:00
var tokens = lc . get ( 'tokenizedMessage' ) ;
2015-02-08 02:01:09 -05:00
2015-02-24 00:33:29 -05:00
lc . set ( 'model' , null ) ;
rc . set ( 'model' , null ) ;
lc . set ( 'parentController' , null ) ;
2015-02-08 02:01:09 -05:00
2015-02-24 00:33:29 -05:00
return tokens ;
2015-02-08 02:01:09 -05:00
}
/ * 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 ) {
2015-02-24 00:33:29 -05:00
// Get the current room.
var room _id = view . get ( 'controller.currentRoom.id' ) ;
2015-02-08 02:01:09 -05:00
} ,
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 ] ;
2015-02-24 00:33:29 -05:00
// Basic Emote Sets
this . _emotes _for _sets ( inner , view , room && room . menu _sets || [ ] ) ;
2015-02-08 02:01:09 -05:00
// 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 ) {
2015-02-24 00:33:29 -05:00
if ( ! set . emotes . hasOwnProperty ( eid ) )
continue ;
2015-01-20 01:53:18 -05:00
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 ) {
2015-02-24 00:33:29 -05:00
var input _el , text , room ;
if ( this . has _bttv ) {
input _el = view . get ( 'element' ) . querySelector ( 'textarea' ) ;
text = input _el . value ;
} else {
room = view . get ( 'controller.currentRoom' ) ;
text = room . get ( 'messageToSend' ) || '' ;
}
2015-01-20 01:53:18 -05:00
2015-02-24 00:33:29 -05:00
text += ( text && text . substr ( - 1 ) !== " " ? " " : "" ) + ( emote . name || emote ) ;
2015-01-20 01:53:18 -05:00
2015-02-24 00:33:29 -05:00
if ( input _el )
input _el . value = text ;
else
room . set ( 'messageToSend' , text ) ;
2014-03-27 22:44:11 -04:00
}
2015-02-24 00:33:29 -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-24 00:33:29 -05:00
} , { "../constants" : 3 } ] , 21 : [ function ( require , module , exports ) {
var FFZ = window . FrankerFaceZ ,
constants = require ( "../constants" ) ,
TWITCH _BASE = "http://static-cdn.jtvnw.net/emoticons/v1/" ,
BANNED _SETS = { "00000turbo" : true } ,
get _emotes = function ( ffz ) {
var Chat = App . _ _container _ _ . lookup ( 'controller:chat' ) ,
room _id = Chat . get ( 'currentRoom.id' ) ,
room = ffz . rooms [ room _id ] ,
tmiSession = room ? room . room . tmiSession : null ,
set _ids = tmiSession && tmiSession . _emotesParser && tmiSession . _emotesParser . emoticonSetIds || "0" ,
user = ffz . get _user ( ) ,
user _sets = user && ffz . users [ user . login ] && ffz . users [ user . login ] . sets || [ ] ;
// Remove the 'default' set.
set _ids = set _ids . split ( "," ) . removeObject ( "0" )
return [ set _ids , user _sets ] ;
} ;
// -------------------
// Initialization
// -------------------
FFZ . prototype . setup _my _emotes = function ( ) {
this . _twitch _emote _sets = { } ;
this . _twitch _set _to _channel = { } ;
if ( localStorage . ffzTwitchSets ) {
try {
this . _twitch _set _to _channel = JSON . parse ( localStorage . ffzTwitchSets ) ;
} catch ( err ) { }
}
}
// -------------------
// Menu Page
// -------------------
FFZ . menu _pages . my _emotes = {
name : "My Emoticons" ,
icon : constants . EMOTE ,
visible : function ( ) {
var emotes = get _emotes ( this ) ;
return emotes [ 0 ] . length > 0 || emotes [ 1 ] . length > 0 ;
} ,
render : function ( view , container ) {
var emotes = get _emotes ( this ) , f = this ;
new RSVP . Promise ( function ( done ) {
var needed _sets = [ ] ;
for ( var i = 0 ; i < emotes [ 0 ] . length ; i ++ ) {
var set _id = emotes [ 0 ] [ i ] ;
if ( ! f . _twitch _emote _sets [ set _id ] )
needed _sets . push ( set _id ) ;
}
RSVP . all ( [
new RSVP . Promise ( function ( d ) {
if ( ! needed _sets . length )
return d ( ) ;
Twitch . api . get ( "chat/emoticon_images" , { emotesets : needed _sets . join ( "," ) } , { version : 3 } )
. done ( function ( data ) {
if ( data . emoticon _sets ) {
for ( var set _id in data . emoticon _sets ) {
if ( ! data . emoticon _sets . hasOwnProperty ( set _id ) )
continue ;
var set = f . _twitch _emote _sets [ set _id ] = f . _twitch _emote _sets [ set _id ] || { } ;
set . emotes = data . emoticon _sets [ set _id ] ;
set . source = "Twitch" ;
}
}
d ( ) ;
} ) . fail ( function ( ) {
d ( ) ;
} ) ;
} ) ,
new RSVP . Promise ( function ( d ) {
if ( ! needed _sets . length )
return d ( ) ;
var promises = [ ] ,
old _needed = needed _sets ,
handle _set = function ( id , name ) {
var set = f . _twitch _emote _sets [ id ] = f . _twitch _emote _sets [ id ] || { } ;
if ( ! name || BANNED _SETS [ name ] )
return ;
if ( name == "turbo" ) {
set . channel = "Twitch Turbo" ;
set . badge = "//cdn.frankerfacez.com/script/turbo_badge.png" ;
return ;
}
// Badge Lookup
promises . push ( new RSVP . Promise ( function ( set , name , dn ) {
Twitch . api . get ( "chat/" + name + "/badges" , null , { version : 3 } )
. done ( function ( data ) {
if ( data . subscriber && data . subscriber . image )
set . badge = data . subscriber . image ;
dn ( ) ;
} ) . fail ( dn ) } . bind ( this , set , name ) ) ) ;
// Mess Up Capitalization
var lname = name . toLowerCase ( ) ,
old _data = FFZ . capitalization [ lname ] ;
if ( old _data && Date . now ( ) - old _data [ 1 ] < 3600000 ) {
set . channel = old _data [ 0 ] ;
return ;
}
promises . push ( new RSVP . Promise ( function ( set , lname , name , dn ) {
if ( ! f . ws _send ( "get_display_name" , lname , function ( success , data ) {
var cap _name = success ? data : name ;
FFZ . capitalization [ lname ] = [ cap _name , Date . now ( ) ] ;
set . channel = cap _name ;
dn ( ) ;
} ) ) {
// Can't use socket.
set . channel = name ;
dn ( ) ;
}
// Timeout
setTimeout ( function ( set , name , dn ) {
if ( ! set . channel )
set . channel = name ;
dn ( ) ;
} . bind ( this , set , name , dn ) , 5000 ) ;
} . bind ( this , set , lname , name ) ) ) ;
} ,
handle _promises = function ( ) {
if ( promises . length )
RSVP . all ( promises ) . then ( d , d ) ;
else
d ( ) ;
} ;
// Process all the sets we already have.
needed _sets = [ ] ;
for ( var i = 0 ; i < old _needed . length ; i ++ ) {
var set _id = old _needed [ i ] ;
if ( f . _twitch _set _to _channel [ set _id ] )
handle _set ( set _id , f . _twitch _set _to _channel [ set _id ] ) ;
else
needed _sets . push ( set _id ) ;
}
if ( needed _sets . length > 0 ) {
f . ws _send ( "twitch_sets" , needed _sets , function ( success , data ) {
needed _sets = [ ] ;
if ( success ) {
for ( var set _id in data ) {
if ( ! data . hasOwnProperty ( set _id ) )
continue ;
f . _twitch _set _to _channel [ set _id ] = data [ set _id ] ;
handle _set ( set _id , data [ set _id ] ) ;
}
localStorage . ffzTwitchSets = JSON . stringify ( f . _twitch _set _to _channel ) ;
}
handle _promises ( ) ;
} ) ;
// Timeout!
setTimeout ( function ( ) {
if ( needed _sets . length )
handle _promises ( ) ;
} , 5000 ) ;
} else
handle _promises ( ) ;
} )
] ) . then ( function ( ) {
var sets = { } ;
for ( var i = 0 ; i < emotes [ 0 ] . length ; i ++ ) {
var set _id = emotes [ 0 ] [ i ] ;
if ( f . _twitch _emote _sets [ set _id ] )
sets [ set _id ] = f . _twitch _emote _sets [ set _id ] ;
}
done ( sets ) ;
} , function ( ) { done ( { } ) ; } )
} ) . then ( function ( twitch _sets ) {
try {
// Don't override a different page. We can wait.
if ( container . getAttribute ( 'data-page' ) != "my_emotes" )
return ;
container . innerHTML = "" ;
var ffz _sets = { } ,
sets = [ ] ;
for ( var set _id in twitch _sets ) {
if ( ! twitch _sets . hasOwnProperty ( set _id ) )
continue ;
var set = twitch _sets [ set _id ] ;
if ( set . channel && set . emotes && set . emotes . length )
sets . push ( [ 1 , set . channel , set ] ) ;
}
sets . sort ( function ( a , b ) {
if ( a [ 0 ] < b [ 0 ] ) return - 1 ;
else if ( a [ 0 ] > b [ 0 ] ) return 1 ;
var an = a [ 1 ] . toLowerCase ( ) ,
bn = b [ 1 ] . toLowerCase ( ) ;
if ( an === "twitch turbo" )
an = "zzz" + an ;
if ( bn === "twitch turbo" )
bn = "zzz" + bn ;
if ( an < bn ) return - 1 ;
else if ( an > bn ) return 1 ;
return 0 ;
} ) ;
for ( var i = 0 ; i < sets . length ; i ++ ) {
var set = sets [ i ] [ 2 ] ,
heading = document . createElement ( 'div' ) ,
menu = document . createElement ( 'div' ) ;
heading . className = 'heading' ;
heading . innerHTML = '<span class="right">' + set . source + '</span>' + FFZ . get _capitalization ( set . channel ) ;
if ( set . badge )
heading . style . backgroundImage = 'url("' + set . badge + '")' ;
menu . className = 'emoticon-grid' ;
menu . appendChild ( heading ) ;
for ( var x = 0 ; x < set . emotes . length ; x ++ ) {
var emote = set . emotes [ x ] ;
var s = document . createElement ( 'span' ) ;
s . className = 'emoticon tooltip' ;
s . style . backgroundImage = 'url("' + TWITCH _BASE + emote . id + '/1.0")' ;
var img _set = 'image-set(url("' + TWITCH _BASE + emote . id + '/1.0") 1x, url("' + TWITCH _BASE + emote . id + '/2.0") 2x, url("' + TWITCH _BASE + emote . id + '/3.0") 4x)' ;
s . style . backgroundImage = '-webkit-' + img _set ;
s . style . backgroundImage = '-moz-' + img _set ;
s . style . backgroundImage = '-ms-' + img _set ;
s . style . backgroundImage = img _set ;
s . title = emote . code ;
s . addEventListener ( 'click' , f . _add _emote . bind ( f , view , emote . code ) ) ;
menu . appendChild ( s ) ;
}
container . appendChild ( menu ) ;
}
if ( ! sets . length ) {
var menu = document . createElement ( 'div' ) ;
menu . className = 'chat-menu-content center' ;
menu . innerHTML = "Error Loading Subscriptions" ;
container . appendChild ( menu ) ;
}
} catch ( err ) {
f . log ( "My Emotes Menu Error" , err ) ;
container . innerHTML = "" ;
var menu = document . createElement ( 'div' ) ,
heading = document . createElement ( 'div' ) ,
p = document . createElement ( 'p' ) ;
heading . className = 'heading' ;
heading . innerHTML = 'Error Loading Menu' ;
menu . appendChild ( heading ) ;
p . className = 'clearfix' ;
p . textContent = err ;
menu . appendChild ( p ) ;
menu . className = 'chat-menu-content' ;
container . appendChild ( menu ) ;
}
} ) ;
}
} ;
} , { "../constants" : 3 } ] , 22 : [ 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 ,
2015-02-10 01:34:23 -05:00
category : "Chat" ,
2015-02-08 02:01:09 -05:00
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 ) {
2015-02-10 01:34:23 -05:00
this . show _message ( message ) ;
2015-02-08 02:01:09 -05:00
}
// ---------------------
// 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-24 00:33:29 -05:00
} , { } ] , 23 : [ function ( require , module , exports ) {
2015-02-08 02:01:09 -05:00
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 ,
2015-02-10 01:34:23 -05:00
category : "Channel Metadata" ,
2015-02-08 02:01:09 -05:00
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 ) ;
}
}
}
2015-02-24 00:33:29 -05:00
} , { "../utils" : 26 } ] , 24 : [ 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' ) ;
2015-02-24 00:33:29 -05:00
s . setAttribute ( 'href' , constants . SERVER + "script/style.css?_=" + Date . now ( ) ) ;
2015-01-20 01:53:18 -05:00
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-24 00:33:29 -05:00
} , { "../constants" : 3 } ] , 25 : [ 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-24 00:33:29 -05:00
} , { "../constants" : 3 , "../utils" : 26 } ] , 26 : [ 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-02-10 01:34:23 -05:00
} ,
brighten = function ( rgb , amount ) {
amount = ( amount === 0 ) ? 0 : ( amount || 1 ) ;
amount = Math . round ( 255 * - ( amount / 100 ) ) ;
var r = Math . max ( 0 , Math . min ( 255 , rgb [ 0 ] - amount ) ) ,
g = Math . max ( 0 , Math . min ( 255 , rgb [ 1 ] - amount ) ) ,
b = Math . max ( 0 , Math . min ( 255 , rgb [ 2 ] - amount ) ) ;
return [ r , g , b ] ;
} ,
rgb _to _css = function ( rgb ) {
return "rgb(" + rgb [ 0 ] + ", " + rgb [ 1 ] + ", " + rgb [ 2 ] + ")" ;
} ,
darken = function ( rgb , amount ) {
amount = ( amount === 0 ) ? 0 : ( amount || 1 ) ;
return brighten ( rgb , - amount ) ;
} ,
get _luminance = function ( rgb ) {
rgb = [ rgb [ 0 ] / 255 , rgb [ 1 ] / 255 , rgb [ 2 ] / 255 ] ;
for ( var i = 0 ; i < rgb . length ; i ++ ) {
if ( rgb [ i ] <= 0.03928 ) {
rgb [ i ] = rgb [ i ] / 12.92 ;
} else {
rgb [ i ] = Math . pow ( ( ( rgb [ i ] + 0.055 ) / 1.055 ) , 2.4 ) ;
}
}
2015-02-24 00:33:29 -05:00
return ( 0.2126 * rgb [ 0 ] ) + ( 0.7152 * rgb [ 1 ] ) + ( 0.0722 * rgb [ 2 ] ) ;
2015-02-08 02:01:09 -05:00
} ;
2015-02-10 01:34:23 -05:00
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 ;
} ,
2015-02-10 01:34:23 -05:00
get _luminance : get _luminance ,
brighten : brighten ,
darken : darken ,
rgb _to _css : rgb _to _css ,
2015-01-20 01:53:18 -05:00
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 ,
2015-02-10 01:34:23 -05:00
2015-02-08 02:01:09 -05:00
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 ) ) ;