2015-06-05 03:59:28 -04:00
var FFZ = window . FrankerFaceZ ,
utils = require ( "./utils" ) ,
2015-07-04 17:06:36 -04:00
constants = require ( "./constants" ) ,
2015-06-05 03:59:28 -04:00
TWITCH _BASE = "http://static-cdn.jtvnw.net/emoticons/v1/" ,
helpers ,
2015-08-21 19:00:48 -04:00
EXPLANATION _TRAIL = '<hr>FFZ is hiding this link because this url shortener is known to be used by Twitch spam bots posting malicious links. Please use caution when visiting shortened links.' ,
2015-07-29 01:03:10 -04:00
SRCSETS = { } ;
build _srcset = function ( id ) {
if ( SRCSETS [ id ] )
return SRCSETS [ id ] ;
var out = SRCSETS [ id ] = TWITCH _BASE + id + "/1.0 1x, " + TWITCH _BASE + id + "/2.0 2x, " + TWITCH _BASE + id + "/3.0 4x" ;
return out ;
} ,
data _to _tooltip = function ( data ) {
var set = data . set ,
set _type = data . set _type ,
owner = data . owner ;
if ( set _type === undefined )
set _type = "Channel" ;
if ( ! set )
return data . code ;
2015-07-31 21:40:51 -04:00
else if ( set === "--global--" ) {
set = "Twitch Global" ;
set _type = null ;
} else if ( set == "--twitch-turbo--" || set == "turbo" || set == "--turbo-faces--" ) {
2015-07-29 01:03:10 -04:00
set = "Twitch Turbo" ;
set _type = null ;
}
return "Emoticon: " + data . code + "\n" + ( set _type ? set _type + ": " : "" ) + set + ( owner ? "\nBy: " + owner . display _name : "" ) ;
} ,
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 )
2015-07-31 21:40:51 -04:00
return code ;
2015-07-29 01:03:10 -04:00
if ( code )
data . code = code ;
this . _twitch _emotes [ id ] = data ;
var tooltip = build _tooltip . bind ( this ) ( id ) ;
var images = document . querySelectorAll ( 'img[data-emote="' + id + '"]' ) ;
for ( var x = 0 ; x < images . length ; x ++ )
images [ x ] . title = tooltip ;
2015-08-21 19:00:48 -04:00
2015-07-31 21:40:51 -04:00
return tooltip ;
2015-07-29 01:03:10 -04:00
} ,
2015-06-05 03:59:28 -04:00
reg _escape = function ( str ) {
return str . replace ( /[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g , "\\$&" ) ;
} ,
2015-07-18 21:10:27 -04:00
LINK = /(?:https?:\/\/)?(?:[-a-zA-Z0-9@:%_\+~#=]+\.)+[a-z]{2,6}\b(?:[-a-zA-Z0-9@:%_\+.~#!?&//=]*)/g ,
2015-06-05 03:59:28 -04: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]" ,
2015-07-29 01:03:10 -04:00
SPLITTER = new RegExp ( SEPARATORS + "*," + SEPARATORS + "*" ) ,
2015-08-21 19:00:48 -04:00
2015-07-29 01:03:10 -04:00
LINK _SPLIT = /^(?:(https?):\/\/)?(?:(.*?)@)?([^\/:]+)(?::(\d+))?(.*?)(?:\?(.*?))?(?:\#(.*?))?$/ ,
YOUTUBE _CHECK = /^(?:https?:\/\/)?(?:m\.|www\.)?youtu(?:be\.com|\.be)\/(?:v\/|watch\/|.*?(?:embed|watch).*?v=)?([a-zA-Z0-9\-_]+)$/ ,
IMGUR _PATH = /^\/(?:gallery\/)?[A-Za-z0-9]+(?:\.(?:png|jpg|jpeg|gif|gifv|bmp))?$/ ,
IMAGE _EXT = /\.(?:png|jpg|jpeg|gif|bmp)$/i ,
IMAGE _DOMAINS = [ ] ,
2015-08-21 19:00:48 -04:00
2015-07-29 01:03:10 -04:00
is _image = function ( href , any _domain ) {
var match = href . match ( LINK _SPLIT ) ;
if ( ! match )
return ;
var domain = match [ 3 ] . toLowerCase ( ) , port = match [ 4 ] ,
path = match [ 5 ] ;
// Don't allow non-standard ports.
if ( port && port !== '80' && port !== '443' )
return false ;
// imgur-specific checks.
if ( domain === 'i.imgur.com' || domain === 'imgur.com' || domain === 'www.imgur.com' || domain === 'm.imgur.com' )
return IMGUR _PATH . test ( path ) ;
2015-08-21 19:00:48 -04:00
return any _domain ? IMAGE _EXT . test ( path ) : IMAGE _DOMAINS . indexOf ( domain ) !== - 1 ;
2015-07-29 01:03:10 -04:00
}
2015-08-21 19:00:48 -04:00
2015-07-29 01:03:10 -04:00
image _iframe = function ( href , extra _class ) {
2015-10-17 18:05:44 -04:00
return '<iframe class="ffz-image-hover' + ( extra _class ? ' ' + extra _class : '' ) + '" allowtransparency="true" src="' + constants . SERVER + 'script/img-proxy.html#' + utils . quote _attr ( href ) + '"></iframe>' ;
2015-07-29 01:03:10 -04:00
} ,
build _link _tooltip = function ( href ) {
var link _data = this . _link _data [ href ] ,
2015-08-21 19:00:48 -04:00
2015-07-29 01:03:10 -04:00
tooltip ;
2015-08-21 19:00:48 -04:00
if ( link _data && link _data . tooltip )
return link _data . tooltip ;
2015-07-29 01:03:10 -04:00
if ( ! link _data )
return "" ;
if ( link _data . type == "youtube" ) {
tooltip = this . settings . link _image _hover ? image _iframe ( link _data . full || href , 'ffz-yt-thumb' ) : '' ;
tooltip += "<b>YouTube: " + utils . sanitize ( link _data . title ) + "</b><hr>" ;
tooltip += "Channel: " + utils . sanitize ( link _data . channel ) + " | " + utils . time _to _string ( link _data . duration ) + "<br>" ;
tooltip += utils . number _commas ( link _data . views || 0 ) + " Views | 👍 " + utils . number _commas ( link _data . likes || 0 ) + " 👎 " + utils . number _commas ( link _data . dislikes || 0 ) ;
} else if ( link _data . type == "strawpoll" ) {
tooltip = "<b>Strawpoll: " + utils . sanitize ( link _data . title ) + "</b><hr><table><tbody>" ;
for ( var key in link _data . items ) {
var votes = link _data . items [ key ] ,
percentage = Math . floor ( ( votes / link _data . total ) * 100 ) ;
tooltip += '<tr><td style="text-align:left">' + utils . sanitize ( key ) + '</td><td style="text-align:right">' + utils . number _commas ( votes ) + "</td></tr>" ;
}
tooltip += "</tbody></table><hr>Total: " + utils . number _commas ( link _data . total ) ;
var fetched = utils . parse _date ( link _data . fetched ) ;
if ( fetched ) {
var age = Math . floor ( ( fetched . getTime ( ) - Date . now ( ) ) / 1000 ) ;
if ( age > 60 )
tooltip += "<br><small>Data was cached " + utils . time _to _string ( age ) + " ago.</small>" ;
}
} else if ( link _data . type == "twitch" ) {
tooltip = "<b>Twitch: " + utils . sanitize ( link _data . display _name ) + "</b><hr>" ;
var since = utils . parse _date ( link _data . since ) ;
if ( since )
tooltip += "Member Since: " + utils . date _string ( since ) + "<br>" ;
tooltip += "<nobr>Views: " + utils . number _commas ( link _data . views ) + "</nobr> | <nobr>Followers: " + utils . number _commas ( link _data . followers ) + "</nobr>" ;
} else if ( link _data . type == "twitch_vod" ) {
tooltip = "<b>Twitch " + ( link _data . broadcast _type == "highlight" ? "Highlight" : "Broadcast" ) + ": " + utils . sanitize ( link _data . title ) + "</b><hr>" ;
tooltip += "By: " + utils . sanitize ( link _data . display _name ) + ( link _data . game ? " | Playing: " + utils . sanitize ( link _data . game ) : " | Not Playing" ) + "<br>" ;
tooltip += "Views: " + utils . number _commas ( link _data . views ) + " | " + utils . time _to _string ( link _data . length ) ;
} else if ( link _data . type == "twitter" ) {
tooltip = "<b>Tweet By: " + utils . sanitize ( link _data . user ) + "</b><hr>" ;
tooltip += utils . sanitize ( link _data . tweet ) ;
} else if ( link _data . type == "reputation" ) {
tooltip = ( this . settings . link _image _hover && is _image ( link _data . full || href , this . settings . image _hover _all _domains ) ) ? image _iframe ( link _data . full || href ) : '' ;
tooltip += '<span style="word-wrap: break-word">' + utils . sanitize ( link _data . full . toLowerCase ( ) ) + '</span>' ;
if ( link _data . trust < 50 || link _data . safety < 50 || ( link _data . tags && link _data . tags . length > 0 ) ) {
tooltip += "<hr>" ;
var had _extra = false ;
if ( link _data . trust < 50 || link _data . safety < 50 ) {
link _data . unsafe = true ;
tooltip += "<b>Potentially Unsafe Link</b><br>" ;
tooltip += "Trust: " + link _data . trust + "% | Child Safety: " + link _data . safety + "%" ;
had _extra = true ;
}
if ( link _data . tags && link _data . tags . length > 0 )
tooltip += ( had _extra ? "<br>" : "" ) + "Tags: " + link _data . tags . join ( ", " ) ;
tooltip += "<br>Data Source: WOT" ;
}
} else if ( link _data . full ) {
tooltip = ( this . settings . link _image _hover && is _image ( link _data . full || href , this . settings . image _hover _all _domains ) ) ? image _iframe ( link _data . full || href ) : '' ;
tooltip += '<span style="word-wrap: break-word">' + utils . sanitize ( link _data . full . toLowerCase ( ) ) + '</span>' ;
}
if ( ! tooltip )
tooltip = '<span style="word-wrap: break-word">' + utils . sanitize ( href . toLowerCase ( ) ) + '</span>' ;
link _data . tooltip = tooltip ;
return tooltip ;
} ,
load _link _data = function ( href , success , data ) {
if ( ! success )
return ;
this . _link _data [ href ] = data ;
data . unsafe = false ;
var tooltip = build _link _tooltip . bind ( this ) ( href ) , links ,
no _trail = href . charAt ( href . length - 1 ) == "/" ? href . substr ( 0 , href . length - 1 ) : null ;
if ( no _trail )
links = document . querySelectorAll ( 'span.message a[href="' + href + '"], span.message a[href="' + no _trail + '"], span.message a[data-url="' + href + '"], span.message a[data-url="' + no _trail + '"]' ) ;
else
links = document . querySelectorAll ( 'span.message a[href="' + href + '"], span.message a[data-url="' + href + '"]' ) ;
if ( ! this . settings . link _info )
return ;
for ( var x = 0 ; x < links . length ; x ++ ) {
if ( data . unsafe )
links [ x ] . classList . add ( 'unsafe-link' ) ;
if ( ! links [ x ] . classList . contains ( 'deleted-link' ) )
links [ x ] . title = tooltip ;
}
} ;
2015-06-05 03:59:28 -04:00
2015-07-04 17:06:36 -04:00
FFZ . SRC _IDS = { } ,
FFZ . src _to _id = function ( src ) {
if ( FFZ . SRC _IDS . hasOwnProperty ( src ) )
return FFZ . SRC _IDS [ src ] ;
var match = /\/emoticons\/v1\/(\d+)\/1\.0/ . exec ( src ) ,
id = match ? parseInt ( match [ 1 ] ) : null ;
if ( id === NaN )
id = null ;
FFZ . SRC _IDS [ src ] = id ;
return id ;
} ;
2015-07-13 21:52:44 -04:00
// ---------------------
2015-07-18 21:10:27 -04:00
// Settings
2015-07-13 21:52:44 -04:00
// ---------------------
var ts = new Date ( 0 ) . toLocaleTimeString ( ) . toUpperCase ( ) ;
FFZ . settings _info . twenty _four _timestamps = {
type : "boolean" ,
value : ts . lastIndexOf ( 'PM' ) === - 1 && ts . lastIndexOf ( 'AM' ) === - 1 ,
category : "Chat Appearance" ,
no _bttv : true ,
name : "24hr Timestamps" ,
help : "Display timestamps in chat in the 24 hour format rather than 12 hour."
} ;
2015-08-28 22:54:12 -04:00
FFZ . settings _info . timestamp _seconds = {
type : "boolean" ,
value : false ,
category : "Chat Appearance" ,
no _bttv : true ,
name : "Timestamp Seconds" ,
help : "Display seconds in chat timestamps."
} ;
2015-07-18 21:10:27 -04:00
FFZ . settings _info . show _deleted _links = {
type : "boolean" ,
value : false ,
category : "Chat Moderation" ,
no _bttv : true ,
name : "Show Deleted Links" ,
help : "Do not delete links based on room settings or link length."
} ;
// ---------------------
// Setup
// ---------------------
FFZ . prototype . setup _tokenization = function ( ) {
2015-07-31 21:40:51 -04:00
// Tooltip Data
this . _twitch _emotes = { } ;
this . _twitch _emote _to _set = { } ;
this . _twitch _set _to _channel = { } ;
this . _link _data = { } ;
2015-08-21 19:00:48 -04:00
2015-07-31 21:40:51 -04:00
this . load _twitch _emote _data ( ) ;
2015-08-21 19:00:48 -04:00
2015-07-18 21:10:27 -04:00
helpers = window . require && window . require ( "ember-twitch-chat/helpers/chat-line-helpers" ) ;
if ( ! helpers )
return this . log ( "Unable to get chat helper functions." ) ;
2015-08-21 19:00:48 -04:00
2015-07-18 21:10:27 -04:00
this . log ( "Hooking Ember chat line helpers." ) ;
var f = this ;
2015-08-21 19:00:48 -04:00
2015-07-18 21:10:27 -04:00
// Timestamp Display
2015-07-13 21:52:44 -04:00
helpers . getTime = function ( e ) {
2015-07-18 21:10:27 -04:00
if ( e === undefined || e === null )
return '?:??' ;
2015-07-13 21:52:44 -04:00
var hours = e . getHours ( ) ,
2015-08-28 22:54:12 -04:00
minutes = e . getMinutes ( ) ,
seconds = e . getSeconds ( ) ;
2015-08-21 19:00:48 -04:00
2015-07-18 21:10:27 -04:00
if ( hours > 12 && ! f . settings . twenty _four _timestamps )
2015-07-13 21:52:44 -04:00
hours -= 12 ;
2015-07-18 21:10:27 -04:00
else if ( hours === 0 && ! f . settings . twenty _four _timestamps )
2015-07-13 21:52:44 -04:00
hours = 12 ;
2015-08-21 19:00:48 -04:00
2015-08-28 22:54:12 -04:00
return hours + ':' + ( minutes < 10 ? '0' : '' ) + minutes + ( f . settings . timestamp _seconds ? ':' + ( seconds < 10 ? '0' : '' ) + seconds : '' ) ;
2015-07-13 21:52:44 -04:00
} ;
2015-07-18 21:10:27 -04:00
// Linkify Messages
helpers . linkifyMessage = function ( tokens , delete _links ) {
var show _deleted = f . settings . show _deleted _links ;
2015-08-21 19:00:48 -04:00
2015-07-18 21:10:27 -04:00
return _ . chain ( tokens ) . map ( function ( token ) {
if ( ! _ . isString ( token ) )
return token ;
2015-08-21 19:00:48 -04:00
2015-07-18 21:10:27 -04:00
var matches = token . match ( LINK ) ;
if ( ! matches || ! matches . length )
return [ token ] ;
2015-08-21 19:00:48 -04:00
2015-07-18 21:10:27 -04:00
return _ . zip (
token . split ( LINK ) ,
_ . map ( matches , function ( e ) {
2015-07-29 01:03:10 -04:00
var long = e . length > 255 ;
if ( ! show _deleted && ( delete _links || long ) )
return { isLink : true , isDeleted : true , isLong : long , href : e } ;
2015-07-18 21:10:27 -04:00
return { isLink : true , href : e } ;
} )
) ;
} ) . flatten ( ) . compact ( ) . value ( ) ;
} ;
}
2015-07-31 21:40:51 -04:00
// ---------------------
// Twitch Emote Data
// ---------------------
FFZ . prototype . load _twitch _emote _data = function ( tries ) {
2015-10-17 18:05:44 -04:00
jQuery . ajax ( constants . SERVER + "script/twitch_emotes.json" , { context : this } )
2015-07-31 21:40:51 -04:00
. done ( function ( data ) {
for ( var set _id in data ) {
var set = data [ set _id ] ;
if ( ! set )
continue ;
this . _twitch _set _to _channel [ set _id ] = set . name ;
for ( var i = 0 , l = set . emotes . length ; i < l ; i ++ )
this . _twitch _emote _to _set [ set . emotes [ i ] ] = set _id ;
}
2015-08-21 19:00:48 -04:00
2015-07-31 21:40:51 -04:00
this . _twitch _set _to _channel [ 0 ] = "--global--" ;
this . _twitch _set _to _channel [ 33 ] = "--turbo-faces--" ;
this . _twitch _set _to _channel [ 42 ] = "--turbo-faces--" ;
} ) . fail ( function ( data ) {
if ( data . status === 404 )
return ;
2015-08-21 19:00:48 -04:00
2015-07-31 21:40:51 -04:00
tries = ( tries || 0 ) + 1 ;
if ( tries < 10 )
setTimeout ( this . load _twitch _emote _data . bind ( this , tries ) , 1000 ) ;
} ) ;
}
2015-06-05 03:59:28 -04:00
// ---------------------
// Tokenization
// ---------------------
2015-07-29 01:03:10 -04:00
FFZ . prototype . tokenize _chat _line = function ( msgObject , prevent _notification , delete _links ) {
2015-06-05 03:59:28 -04:00
if ( msgObject . cachedTokens )
return msgObject . cachedTokens ;
var msg = msgObject . message ,
user = this . get _user ( ) ,
room _id = msgObject . room ,
from _me = user && msgObject . from === user . login ,
emotes = msgObject . tags && msgObject . tags . emotes ,
tokens = [ msg ] ;
// Standard tokenization
2015-07-29 01:03:10 -04:00
if ( helpers && helpers . linkifyMessage ) {
2015-08-21 19:00:48 -04:00
var labels = msgObject . labels || [ ] ,
2015-07-29 01:03:10 -04:00
mod _or _higher = labels . indexOf ( "owner" ) !== - 1 ||
labels . indexOf ( "staff" ) !== - 1 ||
labels . indexOf ( "admin" ) !== - 1 ||
labels . indexOf ( "global_mod" ) !== - 1 ||
labels . indexOf ( "mod" ) !== - 1 ||
2015-08-21 19:00:48 -04:00
msgObject . style === 'admin' ;
2015-07-29 01:03:10 -04:00
tokens = helpers . linkifyMessage ( tokens , delete _links && ! mod _or _higher ) ;
}
2015-08-21 19:00:48 -04:00
2015-07-18 21:10:27 -04:00
if ( user && user . login && helpers && helpers . mentionizeMessage )
2015-06-05 03:59:28 -04:00
tokens = helpers . mentionizeMessage ( tokens , user . login , from _me ) ;
2015-07-06 00:09:21 -04:00
2015-07-18 21:10:27 -04:00
if ( helpers && helpers . emoticonizeMessage )
tokens = helpers . emoticonizeMessage ( tokens , emotes ) ;
2015-07-06 00:09:21 -04:00
if ( this . settings . replace _bad _emotes )
tokens = this . tokenize _replace _emotes ( tokens ) ;
2015-06-05 03:59:28 -04:00
// FrankerFaceZ Extras
tokens = this . _remove _banned ( tokens ) ;
tokens = this . tokenize _emotes ( msgObject . from , room _id , tokens , from _me ) ;
2015-07-04 17:06:36 -04:00
if ( this . settings . parse _emoji )
tokens = this . tokenize _emoji ( tokens ) ;
2015-06-05 03:59:28 -04:00
// Capitalization
var display = msgObject . tags && msgObject . tags [ 'display-name' ] ;
if ( display && display . length )
FFZ . capitalization [ msgObject . from ] = [ display . trim ( ) , Date . now ( ) ] ;
// Mentions!
if ( ! from _me ) {
tokens = this . tokenize _mentions ( tokens ) ;
for ( var i = 0 ; i < tokens . length ; i ++ ) {
var token = tokens [ i ] ;
2015-07-04 17:06:36 -04:00
if ( msgObject . style !== 'whisper' && ( _ . isString ( token ) || ! token . mentionedUser || token . own ) )
2015-06-05 03:59:28 -04:00
continue ;
// We have a mention!
msgObject . ffz _has _mention = true ;
// If we have chat tabs, update the status.
2015-06-10 18:46:04 -04:00
if ( room _id && ! this . has _bttv && this . settings . group _tabs && this . _chatv && this . _chatv . _ffz _tabs ) {
2015-06-05 03:59:28 -04:00
var el = this . _chatv . _ffz _tabs . querySelector ( '.ffz-chat-tab[data-room="' + room _id + '"]' ) ;
if ( el && ! el . classList . contains ( 'active' ) )
el . classList . add ( 'tab-mentioned' ) ;
}
// Display notifications if that setting is enabled. Also make sure
// that we have a chat view because showing a notification when we
// can't actually go to it is a bad thing.
2015-07-04 17:06:36 -04:00
if ( this . _chatv && this . settings . highlight _notifications && ! this . embed _in _dash && ! document . hasFocus ( ) && ! prevent _notification ) {
2015-06-05 03:59:28 -04:00
var room = this . rooms [ room _id ] && this . rooms [ room _id ] . room ,
room _name ;
2015-07-13 21:52:44 -04:00
// Make sure we have UI for this channel.
if ( ( this . settings . group _tabs && ( this . settings . pinned _rooms . indexOf ( room _id ) !== - 1 || this . _chatv . _ffz _host ) ) || room . get ( 'isGroupRoom' ) || room === this . _chatv . get ( 'controller.currentChannelRoom' ) ) {
if ( room && room . get ( 'isGroupRoom' ) )
room _name = room . get ( 'tmiRoom.displayName' ) ;
else
room _name = FFZ . get _capitalization ( room _id ) ;
2015-08-21 19:00:48 -04:00
2015-07-13 21:52:44 -04:00
display = display || Twitch . display . capitalize ( msgObject . from ) ;
2015-08-21 19:00:48 -04:00
2015-07-13 21:52:44 -04:00
if ( msgObject . style === 'action' )
msg = '* ' + display + ' ' + msg ;
else
msg = display + ': ' + msg ;
2015-08-21 19:00:48 -04:00
2015-07-13 21:52:44 -04:00
var f = this ;
if ( msgObject . style === 'whisper' )
this . show _notification (
msg ,
"Twitch Chat Whisper" ,
"ffz_whisper_notice" ,
2015-07-18 21:10:27 -04:00
( this . settings . notification _timeout * 1000 ) ,
2015-07-13 21:52:44 -04:00
function ( ) {
window . focus ( ) ;
}
2015-07-04 17:06:36 -04:00
) ;
2015-07-13 21:52:44 -04:00
else
this . show _notification (
msg ,
"Twitch Chat Mention in " + room _name ,
room _id ,
2015-07-18 21:10:27 -04:00
( this . settings . notification _timeout * 1000 ) ,
2015-07-13 21:52:44 -04:00
function ( ) {
window . focus ( ) ;
var cont = App . _ _container _ _ . lookup ( 'controller:chat' ) ;
room && cont && cont . focusRoom ( room ) ;
}
2015-07-04 17:06:36 -04:00
) ;
2015-07-13 21:52:44 -04:00
}
2015-06-05 03:59:28 -04:00
}
break ;
}
}
msgObject . cachedTokens = tokens ;
return tokens ;
}
2015-07-06 00:09:21 -04:00
FFZ . prototype . tokenize _line = function ( user , room , message , no _emotes , no _emoji ) {
2015-06-05 03:59:28 -04:00
if ( typeof message === "string" )
message = [ message ] ;
if ( helpers && helpers . linkifyMessage )
message = helpers . linkifyMessage ( message ) ;
if ( helpers && helpers . mentionizeMessage ) {
var u = this . get _user ( ) ;
if ( u && u . login )
message = helpers . mentionizeMessage ( message , u . login , user === u . login ) ;
}
2015-07-06 00:09:21 -04:00
if ( ! no _emotes ) {
2015-06-05 03:59:28 -04:00
message = this . tokenize _emotes ( user , room , message ) ;
2015-07-06 00:09:21 -04:00
if ( this . settings . replace _bad _emotes )
message = this . tokenize _replace _emotes ( message ) ;
}
if ( this . settings . parse _emoji && ! no _emoji )
message = this . tokenize _emoji ( message ) ;
2015-06-05 03:59:28 -04:00
return message ;
}
FFZ . prototype . render _tokens = function ( tokens , render _links ) {
2015-07-04 17:06:36 -04:00
var f = this ;
2015-06-05 03:59:28 -04:00
return _ . map ( tokens , function ( token ) {
2015-07-04 17:06:36 -04:00
if ( token . emoticonSrc ) {
2015-08-28 22:54:12 -04:00
var tooltip , src = token . emoticonSrc , srcset , extra ;
2015-07-04 17:06:36 -04:00
if ( token . ffzEmote ) {
var emote _set = f . emote _sets && f . emote _sets [ token . ffzEmoteSet ] ,
emote = emote _set && emote _set . emoticons && emote _set . emoticons [ token . ffzEmote ] ;
tooltip = emote ? utils . sanitize ( f . _emote _tooltip ( emote ) ) : token . altText ;
2015-07-29 01:03:10 -04:00
srcset = emote ? emote . srcSet : token . srcSet ;
extra = ' data-ffz-emote="' + emote . id + '"' ;
2015-07-04 17:06:36 -04:00
} else if ( token . ffzEmoji ) {
var eid = token . ffzEmoji ,
2015-08-28 22:54:12 -04:00
emoji = f . emoji _data && f . emoji _data [ eid ] ,
setting = f . settings . parse _emoji ;
if ( setting === 0 || ( setting === 1 && ! emoji . tw ) || ( setting === 2 && ! emoji . noto ) )
return token . altText ;
2015-07-04 17:06:36 -04:00
2015-08-28 22:54:12 -04:00
tooltip = emoji ? "Emoji: " + token . altText + "\nName: " + emoji . name + ( emoji . short _name ? "\nShort Name: :" + emoji . short _name + ":" : "" ) : token . altText ;
extra = ' data-ffz-emoji="' + eid + '" height="18px"' ;
src = setting === 2 ? token . noto _src : token . tw _src ;
2015-07-04 17:06:36 -04:00
} else {
2015-07-29 01:03:10 -04:00
var id = token . replacedId || FFZ . src _to _id ( token . emoticonSrc ) ,
2015-07-04 17:06:36 -04:00
data = id && f . _twitch _emotes && f . _twitch _emotes [ id ] ;
2015-07-29 01:03:10 -04:00
if ( data )
tooltip = data . tooltip ? data . tooltip : token . altText ;
else {
2015-07-31 21:40:51 -04:00
try {
var set _id = f . _twitch _emote _to _set [ id ] ;
if ( set _id ) {
tooltip = load _emote _data . bind ( f ) ( id , token . altText , true , {
code : token . altText ,
id : id ,
set : f . _twitch _set _to _channel [ set _id ] ,
set _id : set _id
} ) ;
} else {
tooltip = f . _twitch _emotes [ id ] = token . altText ;
f . ws _send ( "twitch_emote" , id , load _emote _data . bind ( f , id , token . altText ) ) ;
}
} catch ( err ) {
f . error ( "Error Generating Emote Tooltip: " + err ) ;
}
2015-07-29 01:03:10 -04:00
}
extra = ' data-emote="' + id + '"' ;
if ( ! constants . EMOTE _REPLACEMENTS [ id ] )
srcset = build _srcset ( id ) ;
2015-07-04 17:06:36 -04:00
}
2015-08-28 22:54:12 -04:00
return '<img class="emoticon tooltip' + ( cls || "" ) + '"' + ( extra || "" ) + ' src="' + utils . quote _attr ( src ) + '" ' + ( srcset ? 'srcset="' + utils . quote _attr ( srcset ) + '" ' : '' ) + 'alt="' + utils . quote _attr ( token . altText ) + '" title="' + utils . quote _attr ( tooltip ) + '">' ;
2015-07-04 17:06:36 -04:00
}
2015-06-05 03:59:28 -04:00
if ( token . isLink ) {
2015-08-21 19:00:48 -04:00
var text = token . title || ( token . isLong && '<long link>' ) || ( token . isShortened && '<shortened link>' ) || ( token . isDeleted && '<deleted link>' ) || token . href ;
2015-06-05 03:59:28 -04:00
if ( ! render _links && render _links !== undefined )
2015-07-29 01:03:10 -04:00
return utils . sanitize ( text ) ;
2015-06-05 03:59:28 -04:00
2015-07-29 01:03:10 -04:00
var href = token . href ,
tooltip , cls = '' ,
2015-08-21 19:00:48 -04:00
2015-07-29 01:03:10 -04:00
ind _at = href . indexOf ( "@" ) ,
ind _sl = href . indexOf ( "/" ) ;
2015-08-21 19:00:48 -04:00
2015-07-29 01:03:10 -04:00
if ( ind _at !== - 1 && ( ind _sl === - 1 || ind _at < ind _sl ) ) {
// E-Mail Link
cls = 'email-link' ;
2015-08-21 19:00:48 -04:00
2015-07-29 01:03:10 -04:00
if ( f . settings . link _info ) {
cls += ' tooltip' ;
tooltip = 'E-Mail ' + href ;
}
2015-08-21 19:00:48 -04:00
2015-07-29 01:03:10 -04:00
href = 'mailto:' + href ;
2015-08-21 19:00:48 -04:00
2015-07-29 01:03:10 -04:00
} else {
// Web Link
if ( ! href . match ( /^https?:\/\// ) )
2015-08-21 19:00:48 -04:00
href = 'http://' + href ;
2015-07-29 01:03:10 -04:00
if ( f . settings . link _info ) {
cls = 'html-tooltip' ;
2015-08-21 19:00:48 -04:00
2015-07-29 01:03:10 -04:00
var data = f . _link _data && f . _link _data [ href ] ;
if ( data ) {
tooltip = data . tooltip ;
if ( data . unsafe )
cls += ' unsafe-link' ;
2015-08-21 19:00:48 -04:00
2015-07-29 01:03:10 -04:00
} else {
f . _link _data = f . _link _data || { } ;
f . _link _data [ href ] = true ;
f . ws _send ( "get_link" , href , load _link _data . bind ( f , href ) ) ;
if ( f . settings . link _image _hover && is _image ( href , f . settings . image _hover _all _domains ) )
tooltip = image _iframe ( href ) ;
}
2015-08-21 19:00:48 -04:00
2015-07-29 01:03:10 -04:00
} else if ( f . settings . link _image _hover ) {
cls = 'html-tooltip' ;
if ( is _image ( href , f . settings . image _hover _all _domains ) )
tooltip = image _iframe ( href ) ;
}
}
2015-08-21 19:00:48 -04:00
2015-07-04 17:06:36 -04:00
2015-07-29 01:03:10 -04:00
// Deleted Links
var actual _href = href ;
2015-08-21 19:00:48 -04:00
if ( token . isShortened ) {
cls = 'shortened-link deleted-link ' + cls ;
tooltip = utils . sanitize ( token . href ) + EXPLANATION _TRAIL ;
href = '#' ;
} else if ( token . isDeleted ) {
2015-07-29 01:03:10 -04:00
cls = 'deleted-link ' + cls ;
tooltip = utils . sanitize ( token . censoredHref || token . href ) ;
href = '#' ;
}
2015-08-21 19:00:48 -04:00
return '<a class="' + cls + '" data-original-url="' + utils . quote _attr ( token . href ) + '" data-url="' + utils . quote _attr ( actual _href ) + '" href="' + utils . quote _attr ( href || '#' ) + '" title="' + utils . quote _attr ( tooltip || '' ) + '" target="_blank">' + utils . sanitize ( text ) + '</a>' ;
2015-06-05 03:59:28 -04:00
}
if ( token . mentionedUser )
2015-07-29 01:03:10 -04:00
return '<span class="' + ( token . own ? "mentioning" : "mentioned" ) + '">' + utils . sanitize ( token . mentionedUser ) + "</span>" ;
2015-06-05 03:59:28 -04:00
if ( token . deletedLink )
return utils . sanitize ( token . text ) ;
return utils . sanitize ( token ) ;
} ) . join ( "" ) ;
}
// ---------------------
// Emoticon Processing
// ---------------------
2015-07-06 00:09:21 -04:00
FFZ . prototype . tokenize _replace _emotes = function ( tokens ) {
// Replace bad Twitch emoticons with custom emoticons.
var f = this ;
if ( _ . isString ( tokens ) )
tokens = [ tokens ] ;
for ( var i = 0 ; i < tokens . length ; i ++ ) {
var token = tokens [ i ] ;
if ( ! token || ! token . emoticonSrc || token . ffzEmote )
continue ;
// Check for a few specific emoticon IDs.
var emote _id = FFZ . src _to _id ( token . emoticonSrc ) ;
if ( constants . EMOTE _REPLACEMENTS . hasOwnProperty ( emote _id ) ) {
2015-07-29 01:03:10 -04:00
token . replacedId = emote _id ;
token . emoticonSrc = constants . EMOTE _REPLACEMENT _BASE + constants . EMOTE _REPLACEMENTS [ emote _id ] ;
2015-07-06 00:09:21 -04:00
}
}
2015-08-21 19:00:48 -04:00
2015-07-06 00:09:21 -04:00
return tokens ;
}
2015-06-05 03:59:28 -04:00
FFZ . prototype . tokenize _title _emotes = function ( tokens ) {
var f = this ,
Channel = App . _ _container _ _ . lookup ( 'controller:channel' ) ,
possible = Channel && Channel . get ( 'product.emoticons' ) ,
emotes = [ ] ;
if ( _ . isString ( tokens ) )
tokens = [ tokens ] ;
// Build a list of emotes that match.
_ . each ( _ . union ( f . _ _twitch _global _emotes || [ ] , possible ) , function ( emote ) {
if ( ! emote || emote . state === "inactive" )
return ;
var r = new RegExp ( "\\b" + emote . regex + "\\b" ) ;
_ . any ( tokens , function ( token ) {
return _ . isString ( token ) && token . match ( r ) ;
} ) && emotes . push ( emote ) ;
} ) ;
// Include Global Emotes~!
if ( f . _ _twitch _global _emotes === undefined || f . _ _twitch _global _emotes === null ) {
f . _ _twitch _global _emotes = false ;
Twitch . api . get ( "chat/emoticon_images" , { emotesets : "0,42" } ) . done ( function ( data ) {
if ( ! data || ! data . emoticon _sets || ! data . emoticon _sets [ 0 ] ) {
f . _ _twitch _global _emotes = [ ] ;
return ;
}
var emotes = f . _ _twitch _global _emotes = [ ] ;
data = data . emoticon _sets [ 0 ] ;
for ( var i = 0 ; i < data . length ; i ++ ) {
var em = data [ i ] ;
emotes . push ( { regex : em . code , url : TWITCH _BASE + em . id + "/1.0" } ) ;
}
if ( f . _cindex )
f . _cindex . ffzFixTitle ( ) ;
} ) . fail ( function ( ) {
setTimeout ( function ( ) { f . _ _twitch _global _emotes = null ; } , 5000 ) ;
} ) ; ;
}
if ( ! emotes . length )
return tokens ;
if ( typeof tokens === "string" )
tokens = [ tokens ] ;
_ . each ( emotes , function ( emote ) {
var eo = { isEmoticon : true , srcSet : emote . url + ' 1x' , emoticonSrc : emote . url , altText : emote . regex } ;
var r = new RegExp ( "\\b" + emote . regex + "\\b" ) ;
tokens = _ . compact ( _ . flatten ( _ . map ( tokens , function ( token ) {
if ( _ . isObject ( token ) )
return token ;
var tbits = token . split ( r ) , bits = [ ] ;
tbits . forEach ( function ( val , ind ) {
bits . push ( val ) ;
if ( ind !== tbits . length - 1 )
bits . push ( eo ) ;
} ) ;
return bits ;
} ) ) ) ;
} ) ;
return tokens ;
}
FFZ . prototype . tokenize _emotes = function ( user , room , tokens , do _report ) {
var f = this ;
// Get our sets.
var sets = this . getEmotes ( user , 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 . emoticons , 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.
2015-07-04 17:06:36 -04:00
if ( typeof tokens === "string" )
2015-06-05 03:59:28 -04:00
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 = {
srcSet : emote . srcSet ,
2015-07-31 17:44:20 -04:00
emoticonSrc : emote . urls [ 1 ] ,
2015-07-04 17:06:36 -04:00
ffzEmote : emote . id ,
ffzEmoteSet : emote . set _id ,
2015-06-05 03:59:28 -04:00
altText : ( emote . hidden ? "???" : emote . name )
} ;
tokens = _ . compact ( _ . flatten ( _ . map ( tokens , function ( token ) {
if ( _ . isObject ( token ) )
return token ;
var tbits = token . split ( emote . regex ) , bits = [ ] ;
while ( tbits . length ) {
var bit = tbits . shift ( ) ;
if ( tbits . length ) {
bit += tbits . shift ( ) ;
if ( bit )
bits . push ( bit ) ;
tbits . shift ( ) ;
bits . push ( eo ) ;
if ( do _report && room )
f . add _usage ( room , emote . id ) ;
} else
bits . push ( bit ) ;
}
return bits ;
} ) ) ) ;
} ) ;
return tokens ;
}
2015-07-04 17:06:36 -04:00
// ---------------------
// Emoji Processing
// ---------------------
FFZ . prototype . tokenize _emoji = function ( tokens ) {
if ( typeof tokens === "string" )
tokens = [ tokens ] ;
if ( ! this . emoji _data )
return tokens ;
var f = this ;
return _ . compact ( _ . flatten ( _ . map ( tokens , function ( token ) {
if ( _ . isObject ( token ) )
return token ;
var tbits = token . split ( constants . EMOJI _REGEX ) , bits = [ ] ;
while ( tbits . length ) {
// Deal with the unmatched string first.
var bit = tbits . shift ( ) ;
bit && bits . push ( bit ) ;
if ( tbits . length ) {
// We have an emoji too, so let's handle that.
var match = tbits . shift ( ) ,
variant = tbits . shift ( ) ;
if ( variant === '\uFE0E' ) {
// Text Variant
bits . push ( match ) ;
} else {
// Find the right image~!
var eid = utils . emoji _to _codepoint ( match , variant ) ,
2015-07-06 00:09:21 -04:00
data = f . emoji _data [ eid ] ;
2015-07-04 17:06:36 -04:00
2015-07-06 00:09:21 -04:00
if ( data )
2015-07-04 17:06:36 -04:00
bits . push ( data . token ) ;
2015-07-06 00:09:21 -04:00
else
bits . push ( match + ( variant || "" ) ) ;
2015-07-04 17:06:36 -04:00
}
}
}
return bits ;
} ) ) ) ;
}
2015-06-05 03:59:28 -04:00
// ---------------------
// Mention Parsing
// ---------------------
FFZ . _regex _cache = { } ;
FFZ . _get _regex = function ( word ) {
return FFZ . _regex _cache [ word ] = FFZ . _regex _cache [ word ] || RegExp ( "\\b" + reg _escape ( word ) + "\\b" , "ig" ) ;
}
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 ;
}
FFZ . prototype . tokenize _mentions = function ( tokens ) {
var mention _words = this . settings . keywords ;
if ( ! mention _words || ! mention _words . length )
return tokens ;
if ( typeof tokens === "string" )
tokens = [ tokens ] ;
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 ;
2015-07-04 17:06:36 -04:00
}
// ---------------------
// Handling Bad Stuff
// ---------------------
FFZ . prototype . _deleted _link _click = function ( e ) {
if ( ! this . classList . contains ( "deleted-link" ) )
return true ;
// Get the URL
var href = this . getAttribute ( 'data-url' ) ,
2015-08-21 19:00:48 -04:00
link = this . getAttribute ( 'data-original-url' ) || href ,
2015-07-04 17:06:36 -04:00
f = FrankerFaceZ . get ( ) ;
// 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 ;
// Now, check for a tooltip.
2015-08-21 19:00:48 -04:00
var link _data = f . _link _data [ href ] ;
2015-07-04 17:06:36 -04:00
if ( link _data && typeof link _data != "boolean" ) {
this . title = link _data . tooltip ;
if ( link _data . unsafe )
this . classList . add ( 'unsafe-link' ) ;
}
// Stop from Navigating
e . preventDefault ( ) ;
2015-06-05 03:59:28 -04:00
}