diff --git a/script.js b/script.js index cb882c2d..713570f2 100644 --- a/script.js +++ b/script.js @@ -190,7 +190,7 @@ FFZ.prototype._legacy_parse_donors = function(data) { this.log("Added donor badge to " + utils.number_commas(count) + " users."); } -},{"./constants":3,"./utils":19}],2:[function(require,module,exports){ +},{"./constants":4,"./utils":22}],2:[function(require,module,exports){ var FFZ = window.FrankerFaceZ, SENDER_REGEX = /(\sdata-sender="[^"]*"(?=>))/; @@ -216,6 +216,7 @@ FFZ.prototype.setup_bttv = function() { this.log("BetterTTV was detected. Hooking."); this.has_bttv = true; + this.track('setCustomVariable', '3', 'BetterTTV', BetterTTV.info.versionString()); // Send Message Behavior var original_send = BetterTTV.chat.helpers.sendMessage, f = this; @@ -266,11 +267,7 @@ FFZ.prototype.setup_bttv = function() { var original_emoticonize = BetterTTV.chat.templates.emoticonize; BetterTTV.chat.templates.emoticonize = function(message, emotes) { var tokens = original_emoticonize(message, emotes), - user = f.users[received_sender], - room = f.rooms[received_room]; - - // Get our sets. - var sets = _.union(user && user.sets || [], room && room.sets || [], f.global_sets), + sets = f.getEmotes(received_sender, received_room), emotes = []; // Build a list of emotes that match. @@ -324,6 +321,66 @@ FFZ.prototype.setup_bttv = function() { this.update_ui_link(); } },{}],3:[function(require,module,exports){ +var FFZ = window.FrankerFaceZ; + +// ----------------- +// Mass Unmod +// ----------------- + +FFZ.chat_commands.massunmod = function(room, args) { + args = args.join(" ").trim(); + + if ( ! args.length ) + return "You must provide a list of users to unmod."; + + args = args.split(/\W*,\W*/); + + var user = this.get_user(); + if ( ! user || ! user.login == room.id ) + return "You must be the broadcaster to use massunmod."; + + if ( args.length > 50 ) + return "Each user you unmod counts as a single message. To avoid being globally banned, please limit yourself to 50 at a time and wait between uses."; + + var count = args.length; + while(args.length) { + var name = args.shift(); + room.room.tmiRoom.sendMessage("/unmod " + name); + } + + return "Sent unmod command for " + count + " users."; +} + +FFZ.chat_commands.massunmod.help = "Usage: /ffz massunmod \nBroadcaster only. Unmod all the users in the provided list."; + + +FFZ.chat_commands.massmod = function(room, args) { + args = args.join(" ").trim(); + + if ( ! args.length ) + return "You must provide a list of users to mod."; + + args = args.split(/\W*,\W*/); + + var user = this.get_user(); + if ( ! user || ! user.login == room.id ) + return "You must be the broadcaster to use massmod."; + + if ( args.length > 50 ) + return "Each user you mod counts as a single message. To avoid being globally banned, please limit yourself to 50 at a time and wait between uses."; + + + var count = args.length; + while(args.length) { + var name = args.shift(); + room.room.tmiRoom.sendMessage("/mod " + name); + } + + return "Sent mod command for " + count + " users."; +} + +FFZ.chat_commands.massmod.help = "Usage: /ffz massmod \nBroadcaster only. Mod all the users in the provided list."; +},{}],4:[function(require,module,exports){ var SVGPATH = '', DEBUG = localStorage.ffzDebugMode == "true" && document.body.classList.contains('ffz-dev'); @@ -335,7 +392,7 @@ module.exports = { ZREKNARF: '' + SVGPATH + '', CHAT_BUTTON: '' + SVGPATH + '' } -},{}],4:[function(require,module,exports){ +},{}],5:[function(require,module,exports){ var FFZ = window.FrankerFaceZ; @@ -359,7 +416,7 @@ FFZ.chat_commands.developer_mode = function(room, args) { FFZ.chat_commands.developer_mode.help = "Usage: /ffz developer_mode \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."; -},{}],5:[function(require,module,exports){ +},{}],6:[function(require,module,exports){ var FFZ = window.FrankerFaceZ; @@ -415,7 +472,7 @@ FFZ.prototype._modify_cview = function(view) { }) }); } -},{}],6:[function(require,module,exports){ +},{}],7:[function(require,module,exports){ var FFZ = window.FrankerFaceZ; @@ -523,12 +580,10 @@ FFZ.chat_commands.capitalization.help = "Usage: /ffz capitalization \nEn FFZ.prototype._emoticonize = function(controller, tokens) { var room_id = controller.get("parentController.model.id"), user_id = controller.get("model.from"), - user = this.users[user_id], - room = this.rooms[room_id], f = this; // Get our sets. - var sets = _.union(user && user.sets || [], room && room.sets || [], f.global_sets), + var sets = this.getEmotes(user_id, room_id), emotes = []; // Build a list of emotes that match. @@ -577,7 +632,7 @@ FFZ.prototype._emoticonize = function(controller, tokens) { return tokens; } -},{}],7:[function(require,module,exports){ +},{}],8:[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*['"]([^'"]+)['"][^}]+(?:}|$)/, @@ -650,8 +705,14 @@ FFZ.prototype.run_command = function(text, room_id) { if ( ! room || !room.room ) return; - if ( ! text ) + 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(); @@ -853,7 +914,25 @@ FFZ.prototype._legacy_load_room_css = function(room_id, callback, data) { output.css = data || null; return this._load_room_json(room_id, callback, output); } -},{"../constants":3,"../utils":19}],8:[function(require,module,exports){ +},{"../constants":4,"../utils":22}],9:[function(require,module,exports){ +var FFZ = window.FrankerFaceZ; + + +// -------------------- +// Initialization +// -------------------- + +FFZ.prototype.setup_router = function() { + this.log("Hooking the Ember router."); + + var f = this; + App.__container__.lookup('router:main').reopen({ + ffzTransition: function() { + f.track_page(); + }.on('didTransition') + }); +} +},{}],10:[function(require,module,exports){ var FFZ = window.FrankerFaceZ; @@ -946,35 +1025,59 @@ FFZ.prototype._modify_viewers = function(controller) { }.property("content.chatters") }); } -},{}],9:[function(require,module,exports){ +},{}],11:[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, constants = require('./constants'), - utils = require('./utils'); + utils = require('./utils'), -var loaded_global = function(set_id, success, data) { - if ( ! success ) - return; + loaded_global = function(set_id, success, data) { + if ( ! success ) + return; - data.global = true; - this.global_sets.push(set_id); -} + 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; -var 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; + 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"; + }, - return margins; -} + 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."); @@ -993,12 +1096,31 @@ FFZ.prototype.setup_emoticons = function() { } +// --------------------- +// 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); } @@ -1022,24 +1144,6 @@ FFZ.prototype.unload_set = function(set_id) { } -var 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"; -} - -var 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"; -} - -var build_css = build_new_css; - - - FFZ.prototype._load_set_json = function(set_id, callback, data) { // Store our set. this.emote_sets[set_id] = data; @@ -1110,7 +1214,7 @@ FFZ.prototype._legacy_load_css = function(set_id, callback, data) { this._load_set_json(set_id, callback, output); } -},{"./constants":3,"./utils":19}],10:[function(require,module,exports){ +},{"./constants":4,"./utils":22}],12:[function(require,module,exports){ // Modify Array and others. require('./shims'); @@ -1178,11 +1282,14 @@ require('./socket'); require('./emoticons'); require('./badges'); +require('./ember/router'); require('./ember/room'); require('./ember/line'); require('./ember/chatview'); require('./ember/viewers'); +require('./tracking'); + require('./debug'); require('./betterttv'); @@ -1196,6 +1303,8 @@ require('./ui/viewer_count'); require('./ui/menu_button'); require('./ui/menu'); +require('./commands'); + // --------------- // Initialization @@ -1249,6 +1358,9 @@ FFZ.prototype.setup = function(delay) { this.setup_emoticons(); this.setup_badges(); + this.setup_piwik(); + + this.setup_router(); this.setup_room(); this.setup_line(); this.setup_chatview(); @@ -1274,7 +1386,7 @@ FFZ.prototype.setup = function(delay) { this.log("Initialization complete in " + duration + "ms"); } -},{"./badges":1,"./betterttv":2,"./debug":4,"./ember/chatview":5,"./ember/line":6,"./ember/room":7,"./ember/viewers":8,"./emoticons":9,"./featurefriday":11,"./shims":12,"./socket":13,"./ui/menu":14,"./ui/menu_button":15,"./ui/notifications":16,"./ui/styles":17,"./ui/viewer_count":18}],11:[function(require,module,exports){ +},{"./badges":1,"./betterttv":2,"./commands":3,"./debug":5,"./ember/chatview":6,"./ember/line":7,"./ember/room":8,"./ember/router":9,"./ember/viewers":10,"./emoticons":11,"./featurefriday":13,"./shims":14,"./socket":15,"./tracking":16,"./ui/menu":17,"./ui/menu_button":18,"./ui/notifications":19,"./ui/styles":20,"./ui/viewer_count":21}],13:[function(require,module,exports){ var FFZ = window.FrankerFaceZ, constants = require('./constants'); @@ -1294,21 +1406,19 @@ FFZ.prototype.check_ff = function(tries) { if ( ! tries ) this.log("Checking for Feature Friday data..."); - var f = this; - jQuery.getJSON(constants.SERVER + "script/event.json") + jQuery.ajax(constants.SERVER + "script/event.json", {cache: false, dataType: "json", context: this}) .done(function(data) { - return f._load_ff(data); - + return this._load_ff(data); }).fail(function(data) { if ( data.status == 404 ) - return f._load_ff(null); + return this._load_ff(null); tries = tries || 0; tries++; if ( tries < 10 ) - return setTimeout(f.check_ff.bind(this, tries), 250); + return setTimeout(this.check_ff.bind(this, tries), 250); - return f._load_ff(null); + return this._load_ff(null); }); } @@ -1335,7 +1445,7 @@ FFZ.prototype._feature_friday_ui = function(room_id, parent, view) { return; - var ff = this.feature_friday, + var ff = this.feature_friday, f = this, btnc = document.createElement('div'), btn = document.createElement('a'); @@ -1353,6 +1463,9 @@ FFZ.prototype._feature_friday_ui = function(room_id, parent, view) { btn.target = "_new"; btn.innerHTML = "" + message + ""; + // Track the number of users to click this button. + btn.addEventListener('click', function() { f.track('trackLink', this.href, 'link'); }); + btnc.appendChild(btn); parent.appendChild(btnc); } @@ -1420,7 +1533,7 @@ FFZ.prototype._update_ff_name = function(name) { if ( this.feature_friday ) this.feature_friday.display_name = name; } -},{"./constants":3}],12:[function(require,module,exports){ +},{"./constants":4}],14:[function(require,module,exports){ Array.prototype.equals = function (array) { // if the other array is a falsy value, return if (!array) @@ -1446,7 +1559,7 @@ Array.prototype.equals = function (array) { } -},{}],13:[function(require,module,exports){ +},{}],15:[function(require,module,exports){ var FFZ = window.FrankerFaceZ; FFZ.prototype._ws_open = false; @@ -1540,7 +1653,139 @@ FFZ.prototype.ws_send = function(func, data, callback) { this._ws_sock.send(request + " " + func + data); return request; } -},{}],14:[function(require,module,exports){ +},{}],16:[function(require,module,exports){ +var FFZ = window.FrankerFaceZ, + constants = require('./constants'), + PIWIK = ("https:" == document.location.protocol ? 'https:' : 'http:') + '//sir.stendec.me/ffz_piwik/'; + + +// -------------------- +// Initialization +// -------------------- + +FFZ.prototype.setup_piwik = function() { + if ( window._paq != undefined ) { + this.log("Piwik is already present. Disabling analytics."); + this._tracking = false; + return; + } + + if ( localStorage['ffzTracking'] == "false" ) { + this.log("The user has opted out of tracking. Disabling analytics."); + this._tracking = false; + return; + } + + this.log("Initializing Piwik."); + this._tracking = true; + var _paq = window._paq = []; + + _paq.push(['setSiteId', 1]); + _paq.push(['setTrackerUrl', PIWIK + 'piwik.php']); + + if ( this.has_bttv ) + _paq.push(['setCustomVariable', '3', 'BetterTTV', BetterTTV.info.versionString()]); + + var user = this.get_user(), f = this; + if ( user ) { + _paq.push(['setCustomVariable', '1', 'Partnered', user.is_partner ? "Yes" : "No"]) + _paq.push(['setCustomVariable', '2', 'User Type', user.is_staff ? "Staff" : (user.is_admin ? "Admin" : "User")]); + _paq.push(['setUserId', user.login]); + + Twitch.api.get("channels/" + user.login) + .done(function(data) { + if ( data.logo ) + f.track('setCustomVariable', '4', 'Avatar', data.logo); + }).always(function() { f.track_page(); }); + + } else + this.track_page(); + + // If someone turned analytics back ON, track that. + if ( localStorage['ffzTracking'] == "true" ) { + this.track('trackEvent', 'Analytics', 'Enable'); + localStorage.removeItem('ffzTracking'); + } + + var script = document.createElement('script'); + script.type = 'text/javascript'; + script.defer = true; + script.async = true; + script.src = PIWIK + 'piwik.js'; + document.head.appendChild(script); +} + + +// -------------------- +// Command +// -------------------- + +FFZ.chat_commands.analytics = function(room, args) { + var enabled, args = args && args.length ? args[0].toLowerCase() : null; + if ( args == "y" || args == "yes" || args == "true" || args == "on" ) + enabled = true; + else if ( args == "n" || args == "no" || args == "false" || args == "off" ) + enabled = false; + + if ( enabled === undefined ) + return "Analytics are currently " + (localStorage.ffzTracking != "false" ? "enabled." : "disabled."); + + // Track that someone turned off analytics. + if ( this._tracking && ! enabled && localStorage.ffzTracking != "false" ) + this.track('trackEvent', 'Analytics', 'Disable'); + + localStorage.ffzTracking = enabled; + + return "Analytics are now " + (enabled ? "enabled" : "disabled") + ". Please refresh your browser."; +} + +FFZ.chat_commands.analytics.help = "Usage: /ffz analytics \nEnable or disable FrankerFaceZ analytics. We collect some data about your browser and how you use FrankerFaceZ to help us improve the script. Turn off analytics if you'd rather we not."; + + + +// -------------------- +// Tracking Helpers +// -------------------- + +FFZ.prototype.track = function() { + if ( ! this._tracking ) + return; + + window._paq && _paq.push(Array.prototype.slice.call(arguments)); +} + + +FFZ.prototype.track_page = function() { + if ( ! this._tracking ) + return; + + if ( this._old_url ) + this.track('setReferrerUrl', this._old_url); + + this._old_url = document.location.toString(); + this.track('setCustomUrl', this._old_url); + + this.track('deleteCustomVariable', '1', 'page'); + this.track('deleteCustomVariable', '3', 'page'); + + var routes = App.__container__.resolve('router:main').router.currentHandlerInfos; + if ( ! routes || routes.length == 0 ) + return; + + var last = routes[routes.length - 1]; + if ( last.name == "channel.index" && last.context ) { + var following = last.context.get("isFollowing.isFollowing"); + if ( following !== undefined && following !== null ) + this.track('setCustomVariable', '1', 'Following', (following ? "Yes" : "No"), 'page'); + + var game = last.context.get("game"); + if ( game ) + this.track("setCustomVariable", "3", "Game", game, "page"); + + this.track("trackPageView", document.title); + } +} +},{"./constants":4}],17:[function(require,module,exports){ var FFZ = window.FrankerFaceZ; @@ -1593,6 +1838,7 @@ FFZ.prototype.build_ui_popup = function(view) { room = this.rooms[room_id]; this.log("Menu for Room: " + room_id, room); + this.track('trackEvent', 'Menu', 'Open', room_id); // Add the header and ad button. var btn = document.createElement('a'); @@ -1683,7 +1929,7 @@ FFZ.prototype._add_emote = function(view, emote) { room.set('messageToSend', current_text + (emote.name || emote)); } -},{}],15:[function(require,module,exports){ +},{}],18:[function(require,module,exports){ var FFZ = window.FrankerFaceZ, constants = require('../constants'); @@ -1733,7 +1979,7 @@ FFZ.prototype.update_ui_link = function(link) { link.classList.toggle('dark', dark); link.classList.toggle('blue', blue); } -},{"../constants":3}],16:[function(require,module,exports){ +},{"../constants":4}],19:[function(require,module,exports){ var FFZ = window.FrankerFaceZ; FFZ.prototype.show_notification = function(message) { @@ -1749,7 +1995,7 @@ FFZ.prototype.show_notification = function(message) { FFZ.ws_commands.message = function(message) { this.show_notification(message); } -},{}],17:[function(require,module,exports){ +},{}],20:[function(require,module,exports){ var FFZ = window.FrankerFaceZ, constants = require('../constants'); @@ -1774,7 +2020,7 @@ FFZ.prototype.setup_css = function() { } }; } -},{"../constants":3}],18:[function(require,module,exports){ +},{"../constants":4}],21:[function(require,module,exports){ var FFZ = window.FrankerFaceZ, constants = require('../constants'), utils = require('../utils'); @@ -1811,7 +2057,7 @@ FFZ.ws_commands.viewers = function(data) { jQuery(view_count).tipsy(); } } -},{"../constants":3,"../utils":19}],19:[function(require,module,exports){ +},{"../constants":4,"../utils":22}],22:[function(require,module,exports){ var FFZ = window.FrankerFaceZ, constants = require('./constants'); @@ -1842,4 +2088,4 @@ module.exports = { return parts.join("."); } } -},{"./constants":3}]},{},[10]);window.ffz = new FrankerFaceZ()}(window)); \ No newline at end of file +},{"./constants":4}]},{},[12]);window.ffz = new FrankerFaceZ()}(window)); \ No newline at end of file diff --git a/script.min.js b/script.min.js index e9336ab4..33d138f7 100644 --- a/script.min.js +++ b/script.min.js @@ -1 +1,2 @@ -!function(e){!function t(e,o,s){function n(i,a){if(!o[i]){if(!e[i]){var l="function"==typeof require&&require;if(!a&&l)return l(i,!0);if(r)return r(i,!0);throw new Error("Cannot find module '"+i+"'")}var c=o[i]={exports:{}};e[i][0].call(c.exports,function(t){var o=e[i][1][t];return n(o?o:t)},c,c.exports,t,e,o,s)}return o[i].exports}for(var r="function"==typeof require&&require,i=0;ie?this._legacy_add_donors(e):void 0):void 0})},o.prototype._legacy_parse_donors=function(e){var t=0;if(null!=e)for(var o=e.trim().split(/\W+/),s=0;s))/;t.prototype.find_bttv=function(t,o){return this.has_bttv=!1,e.BTTVLOADED?this.setup_bttv():void(o>=6e4?this.log("BetterTTV was not detected after 60 seconds."):setTimeout(this.find_bttv.bind(this,t,(o||0)+t),t))},t.prototype.setup_bttv=function(){this.log("BetterTTV was detected. Hooking."),this.has_bttv=!0;var e=BetterTTV.chat.helpers.sendMessage,t=this;BetterTTV.chat.helpers.sendMessage=function(o){var s=o.split(" ",1)[0].toLowerCase();return"/ffz"!==s?e(o):void t.run_command(o.substr(5),BetterTTV.chat.store.currentRoom)};var s,n=BetterTTV.chat.handlers.privmsg;BetterTTV.chat.handlers.privmsg=function(e,t){s=e;var o=n(e,t);return s=null,o};var r=BetterTTV.chat.templates.privmsg;BetterTTV.chat.templates.privmsg=function(e,n,i,a,l){t.bttv_badges(l);var c=r(e,n,i,a,l);return c.replace(o,'$1 data-room="'+s+'"')};var i,a=BetterTTV.chat.templates.message;BetterTTV.chat.templates.message=function(e,t,o,s){i=e;var n=a(e,t,o,s);return i=null,n};var l=BetterTTV.chat.templates.emoticonize;BetterTTV.chat.templates.emoticonize=function(e,o){var n=l(e,o),r=t.users[i],a=t.rooms[s],c=_.union(r&&r.sets||[],a&&a.sets||[],t.global_sets),o=[];return _.each(c,function(e){var s=t.emote_sets[e];s&&_.each(s.emotes,function(e){_.any(n,function(t){return _.isString(t)&&t.match(e.regex)})&&o.push(e)})}),o.length?(_.each(o,function(e){var t=[''+e.name+''],o=n;if(n=[],!o||!o.length)return n;for(var s=0;s'+o+"",CHAT_BUTTON:''+o+""}},{}],4:[function(){var t=e.FrankerFaceZ;t.chat_commands.developer_mode=function(e,t){var o,t=t&&t.length?t[0].toLowerCase():null;return"y"==t||"yes"==t||"true"==t||"on"==t?o=!0:("n"==t||"no"==t||"false"==t||"off"==t)&&(o=!1),void 0===o?"Developer Mode is currently "+("true"==localStorage.ffzDebugMode?"enabled.":"disabled."):(localStorage.ffzDebugMode=o,"Developer Mode is now "+(o?"enabled":"disabled")+". Please refresh your browser.")},t.chat_commands.developer_mode.help="Usage: /ffz developer_mode \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."},{}],5:[function(){var t=e.FrankerFaceZ;t.prototype.setup_chatview=function(){this.log("Hooking the Ember Chat view.");var e=App.__container__.resolve("view:chat");this._modify_cview(e),e.create().destroy();for(var t in Ember.View.views)if(Ember.View.views.hasOwnProperty(t)){var o=Ember.View.views[t];o instanceof e&&(this.log("Adding UI link manually to Chat view.",o),o.$(".textarea-contain").append(this.build_ui_link(o)))}},t.prototype._modify_cview=function(e){var t=this;e.reopen({didInsertElement:function(){this._super(),this.$()&&this.$(".textarea-contain").append(t.build_ui_link(this))},willClearRender:function(){this._super(),this.$(".ffz-ui-toggle").remove()},ffzUpdateLink:Ember.observer("controller.currentRoom",function(){t.update_ui_link()})})}},{}],6:[function(){var t=e.FrankerFaceZ;t.prototype.setup_line=function(){this.log("Hooking the Ember Line controller.");var e=App.__container__.resolve("controller:line"),t=this;e.reopen({tokenizedMessage:function(){return t._emoticonize(this,this._super())}.property("model.message","isModeratorOrHigher","controllers.emoticons.emoticons.[]")}),this.log("Hooking the Ember Line view.");var e=App.__container__.resolve("view:line");e.reopen({didInsertElement:function(){this._super();var e=this.get("element"),o=this.get("context.model.from");e.setAttribute("data-room",this.get("context.parentController.content.id")),e.setAttribute("data-sender",o),t.render_badge(this),"false"!=localStorage.ffzCapitalize&&t.capitalize(this,o)}})},t.capitalization={},t._cap_fetching=0,t.get_capitalization=function(e,o){e=e.toLowerCase();var s=t.capitalization[e];return s&&Date.now()-s[1]<36e5?s[0]:(t._cap_fetching<5&&(t._cap_fetching++,Twitch.api.get("users/"+e).always(function(s){var n=s.display_name||e;t.capitalization[e]=[n,Date.now()],t._cap_fetching--,o&&o(n)})),s?s[0]:e)},t.prototype.capitalize=function(e,o){var s=t.get_capitalization(o,this.capitalize.bind(this,e));s&&e.$(".from").text(s)},t.chat_commands.capitalization=function(e,t){var o,t=t&&t.length?t[0].toLowerCase():null;return"y"==t||"yes"==t||"true"==t||"on"==t?o=!0:("n"==t||"no"==t||"false"==t||"off"==t)&&(o=!1),void 0===o?"Chat Name Capitalization is currently "+("false"!=localStorage.ffzCapitalize?"enabled.":"disabled."):(localStorage.ffzCapitalize=o,"Chat Name Capitalization is now "+(o?"enabled.":"disabled."))},t.chat_commands.capitalization.help="Usage: /ffz capitalization \nEnable or disable Chat Name Capitalization. This setting does not work with BetterTTV.",t.prototype._emoticonize=function(e,t){var o=e.get("parentController.model.id"),s=e.get("model.from"),n=this.users[s],r=this.rooms[o],i=this,a=_.union(n&&n.sets||[],r&&r.sets||[],i.global_sets),l=[];return _.each(a,function(e){var o=i.emote_sets[e];o&&_.each(o.emotes,function(e){_.any(t,function(t){return _.isString(t)&&t.match(e.regex)})&&l.push(e)})}),l.length?("string"==typeof t&&(t=[t]),_.each(l,function(e){var o={isEmoticon:!0,cls:e.klass,emoticonSrc:e.url,altText:e.hidden?"???":e.name};t=_.compact(_.flatten(_.map(t,function(t){if(_.isObject(t))return t;var s=t.split(e.regex),n=[];return s.forEach(function(e,t){n.push(e),t!==s.length-1&&n.push(o)}),n})))}),t):t}},{}],7:[function(t){var o=e.FrankerFaceZ,s=/\.([\w\-_]+)\s*?\{content:\s*?"([^"]+)";\s*?background-image:\s*?url\("([^"]+)"\);\s*?height:\s*?(\d+)px;\s*?width:\s*?(\d+)px;\s*?margin:([^;}]+);?([^}]*)\}/gm,n=/[^\n}]*\.badges\s+\.moderator\s*{\s*background-image:\s*url\(\s*['"]([^'"]+)['"][^}]+(?:}|$)/,r=/^_([^_]+)_\d+$/,i=t("../constants"),a=t("../utils"),l=function(e){return e.moderator_badge?'.chat-line[data-room="'+e.id+'"] .badges .moderator { background-image:url("'+e.moderator_badge+'") !important; }':""};o.prototype.setup_room=function(){this.rooms={},this.log("Creating room style element.");var e=this._room_style=document.createElement("style");e.id="ffz-room-css",document.head.appendChild(e),this.log("Hooking the Ember Room model.");var t=App.__container__.resolve("model:room");this._modify_room(t);var o=t.instances;for(var s in o)if(o.hasOwnProperty(s)){var n=o[s];this.add_room(n.id,n),this._modify_room(n)}},o.chat_commands={},o.prototype.room_message=function(e,t){var o=t.split("\n");if(this.has_bttv)for(var s=0;so?this._legacy_add_room(e,t,o):void 0)})},o.prototype._legacy_load_room_css=function(e,t,o){var i=e,a=i.match(r);a&&a[1]&&(i=a[1]);var l={id:e,menu_sets:[i],sets:[i],moderator_badge:null,css:null};return o&&(o=o.replace(s,"").trim()),o&&(o=o.replace(n,function(e,t){return l.moderator_badge||"modicon.png"!==t.substr(-11)?e:(l.moderator_badge=t,"")})),l.css=o||null,this._load_room_json(e,t,l)}},{"../constants":3,"../utils":19}],8:[function(){var t=e.FrankerFaceZ;t.prototype.setup_viewers=function(){this.log("Hooking the Ember Viewers controller.");var e=App.__container__.resolve("controller:viewers");this._modify_viewers(e)},t.prototype._modify_viewers=function(e){var o=this;e.reopen({lines:function(){var e=this._super(),s=[],n={},r=null,i=App.__container__.lookup("controller:channel"),a=this.get("parentController.model.id"),l=i&&i.get("id");if(l){var c=i.get("display_name");c&&(t.capitalization[l]=[c,Date.now()])}a!=l&&(l=null);for(var d=0;do?this._legacy_load_set(e,t,o):t&&t(!1))})},o.prototype._legacy_load_css=function(e,t,o){var n={},r={id:e,emotes:n,extra_css:null},i=this;o.replace(s,function(e,t,o,s,r,l,c,d){r=parseInt(r),l=parseInt(l),c=a(c,r);var u="."===s.substr(s.lastIndexOf("/")+1,1),h=++i._last_emote_id,f={id:h,hidden:u,name:o,height:r,width:l,url:s,margins:c,extra_css:d};return n[h]=f,""}),this._load_set_json(e,t,r)}},{"./constants":3,"./utils":19}],10:[function(t){t("./shims");var o=e.FrankerFaceZ=function(){o.instance=this,this.initialize()};o.get=function(){return o.instance};var s=o.version_info={major:3,minor:0,revision:0,toString:function(){return[s.major,s.minor,s.revision].join(".")+(s.extra||"")}};o.prototype.log=function(e,t,o){e="FFZ: "+e+(o?" -- "+JSON.stringify(t):""),void 0!==t&&console.groupCollapsed&&console.dir?(console.groupCollapsed(e),-1!==navigator.userAgent.indexOf("Firefox/")?console.log(t):console.dir(t),console.groupEnd(e)):console.log(e)},o.prototype.get_user=function(){if(e.PP&&PP.login)return PP;if(e.App){var t=App.__container__.lookup("controller:navigation");return t?t.get("userData"):void 0}},t("./socket"),t("./emoticons"),t("./badges"),t("./ember/room"),t("./ember/line"),t("./ember/chatview"),t("./ember/viewers"),t("./debug"),t("./betterttv"),t("./featurefriday"),t("./ui/styles"),t("./ui/notifications"),t("./ui/viewer_count"),t("./ui/menu_button"),t("./ui/menu"),o.prototype.initialize=function(t,o){var s=void 0!=e.App&&void 0!=App.__container__&&void 0!=App.__container__.resolve("model:room");return s?void this.setup(o):(t=t||10,void(o>=6e4?this.log('Twitch application not detected in "'+location.toString()+'". Aborting.'):setTimeout(this.initialize.bind(this,t,(o||0)+t),t)))},o.prototype.setup=function(t){var s=e.performance&&performance.now?performance.now():Date.now();this.log("Found Twitch application after "+(t||0)+' ms in "'+location+'". Initializing FrankerFaceZ version '+o.version_info),this.users={};for(var n in localStorage)"ffz_"==n.substr(0,4)&&localStorage.removeItem(n);var r=this.get_user();r&&r.name&&(o.capitalization[r.login]=[r.name,Date.now()]);try{this.ws_create(),this.setup_emoticons(),this.setup_badges(),this.setup_room(),this.setup_line(),this.setup_chatview(),this.setup_viewers(),this.setup_css(),this.setup_menu(),this.find_bttv(10),this.check_ff()}catch(i){return void this.log("An error occurred while starting FrankerFaceZ: "+i)}e.console&&console.time&&console.timeEnd("FrankerFaceZ Initialization");var a=e.performance&&performance.now?performance.now():Date.now(),l=a-s;this.log("Initialization complete in "+l+"ms")}},{"./badges":1,"./betterttv":2,"./debug":4,"./ember/chatview":5,"./ember/line":6,"./ember/room":7,"./ember/viewers":8,"./emoticons":9,"./featurefriday":11,"./shims":12,"./socket":13,"./ui/menu":14,"./ui/menu_button":15,"./ui/notifications":16,"./ui/styles":17,"./ui/viewer_count":18}],11:[function(t){var o=e.FrankerFaceZ,s=t("./constants");o.prototype.feature_friday=null,o.prototype.check_ff=function(e){e||this.log("Checking for Feature Friday data...");var t=this;jQuery.getJSON(s.SERVER+"script/event.json").done(function(e){return t._load_ff(e)}).fail(function(o){return 404==o.status?t._load_ff(null):(e=e||0,e++,10>e?setTimeout(t.check_ff.bind(this,e),250):t._load_ff(null))})},o.ws_commands.reload_ff=function(){this.check_ff()},o.prototype._feature_friday_ui=function(e,t,o){if(this.feature_friday&&this.feature_friday.channel!=e){this._emotes_for_sets(t,o,[this.feature_friday.set],"Feature Friday");var s=App.__container__.lookup("controller:channel");if(!s||s.get("id")!=this.feature_friday.channel){var n=this.feature_friday,r=document.createElement("div"),i=document.createElement("a");r.className="chat-menu-content",r.style.textAlign="center";var a=n.display_name+(n.live?" is live now!":"");i.className="button primary",i.classList.toggle("live",n.live),i.classList.toggle("blue",this.has_bttv&&BetterTTV.settings.get("showBlueButtons")),i.href="http://www.twitch.tv/"+n.channel,i.title=a,i.target="_new",i.innerHTML=""+a+"",r.appendChild(i),t.appendChild(r)}}},o.prototype._load_ff=function(e){if(this.feature_friday){this.global_sets.removeObject(this.feature_friday.set);var t=this.emote_sets[this.feature_friday.set];t&&(t.global=!1),this.feature_friday=null,this.update_ui_link()}e&&e.set&&e.channel&&(this.feature_friday={set:e.set,channel:e.channel,live:!1,display_name:o.get_capitalization(e.channel,this._update_ff_name.bind(this))},this.global_sets.push(e.set),this.load_set(e.set,this._update_ff_set.bind(this)),this._update_ff_live())},o.prototype._update_ff_live=function(){if(this.feature_friday){var e=this;Twitch.api.get("streams/"+this.feature_friday.channel).done(function(t){e.feature_friday.live=null!=t.stream,e.update_ui_link()}).always(function(){e.feature_friday.timer=setTimeout(e._update_ff_live.bind(e),12e4)})}},o.prototype._update_ff_set=function(e,t){t&&(t.global=!0)},o.prototype._update_ff_name=function(e){this.feature_friday&&(this.feature_friday.display_name=e)}},{"./constants":3}],12:[function(){Array.prototype.equals=function(e){if(!e)return!1;if(this.length!=e.length)return!1;for(var t=0,o=this.length;o>t;t++)if(this[t]instanceof Array&&e[t]instanceof Array){if(!this[t].equals(e[t]))return!1}else if(this[t]!=e[t])return!1;return!0}},{}],13:[function(){var t=e.FrankerFaceZ;t.prototype._ws_open=!1,t.prototype._ws_delay=0,t.ws_commands={},t.prototype.ws_create=function(){var e=this;this._ws_last_req=0,this._ws_callbacks={};var o=this._ws_sock=new WebSocket("ws://ffz.stendec.me/");o.onopen=function(){e._ws_open=!0,e._ws_delay=0,e.log("Socket connected.");var t=e.get_user();t&&e.ws_send("setuser",t.login);for(var o in e.rooms)e.ws_send("sub",o)},o.onclose=function(){e.log("Socket closed."),e._ws_open=!1,e._ws_delay<3e4&&(e._ws_delay+=5e3),setTimeout(e.ws_create.bind(e),e._ws_delay)},o.onmessage=function(o){var s,n,r=o.data.indexOf(" "),i=o.data.substr(r+1),a=parseInt(o.data.slice(0,r));if(r=i.indexOf(" "),-1===r&&(r=i.length),s=i.slice(0,r),i=i.substr(r+1),i&&(n=JSON.parse(i)),-1===a){var l=t.ws_commands[s];l?l.bind(e)(n):e.log("Invalid command: "+s,n)}else{var c="True"===s,d=e._ws_callbacks[a];e.log("Socket Reply to "+a+" - "+(c?"SUCCESS":"FAIL"),n),d&&(delete e._ws_callbacks[a],d(c,n))}}},t.prototype.ws_send=function(e,t,o){if(!this._ws_open)return!1;var s=++this._ws_last_req;return t=void 0!==t?" "+JSON.stringify(t):"",o&&(this._ws_callbacks[s]=o),this._ws_sock.send(s+" "+e+t),s}},{}],14:[function(){var t=e.FrankerFaceZ;t.prototype.setup_menu=function(){this.log("Installing mouse-up event to auto-close menus.");var e=this;jQuery(document).mouseup(function(t){var o,s=e._popup;s&&(s=jQuery(s),o=s.parent(),o.is(t.target)||0!==o.has(t.target).length||(s.remove(),delete e._popup))})},t.prototype.build_ui_popup=function(e){var t=this._popup;if(t)return t.parentElement.removeChild(t),void delete this._popup;var o=document.createElement("div"),s=document.createElement("div");o.className="emoticon-selector chat-menu ffz-ui-popup",s.className="emoticon-selector-box dropmenu",o.appendChild(s);var n=e.get("controller.currentRoom.id"),r=this.rooms[n];this.log("Menu for Room: "+n,r);var i=document.createElement("a");i.className="button glyph-only ffz-button",i.title="Advertise for FrankerFaceZ in chat!",i.href="#",i.innerHTML='';var a=document.createElement("div");a.className="list-header first",a.appendChild(i),a.appendChild(document.createTextNode("FrankerFaceZ")),s.appendChild(a);var l=this._emotes_for_sets(s,e,r&&r.menu_sets||[]);0===l?i.addEventListener("click",this._add_emote.bind(this,e,"To use custom emoticons in tons of channels, get FrankerFaceZ from http://www.frankerfacez.com")):i.addEventListener("click",this._add_emote.bind(this,e,"To view this channel's emoticons, get FrankerFaceZ from http://www.frankerfacez.com")),this._feature_friday_ui(n,s,e),this._popup=o,s.style.maxHeight=Math.max(300,e.$().height()-171)+"px",e.$(".chat-interface").append(o)},t.prototype._emotes_for_sets=function(e,t,o,s,n){if(null!=s){var r=document.createElement("div");r.className="list-header",r.appendChild(document.createTextNode(s)),n&&r.appendChild(n),e.appendChild(r)}var i=document.createElement("div"),a=0;i.className="emoticon-grid";for(var l=0;l0){n=!0;break}}e.classList.toggle("no-emotes",!n),e.classList.toggle("live",a),e.classList.toggle("dark",r),e.classList.toggle("blue",i)}},{"../constants":3}],16:[function(){var t=e.FrankerFaceZ;t.prototype.show_notification=function(t){e.noty({text:t,theme:"ffzTheme",layout:"bottomCenter",closeWith:["button"]}).show()},t.ws_commands.message=function(e){this.show_notification(e)}},{}],17:[function(t){var o=e.FrankerFaceZ,s=t("../constants");o.prototype.setup_css=function(){this.log("Injecting main FrankerFaceZ CSS.");var e=this._main_style=document.createElement("link");e.id="ffz-ui-css",e.setAttribute("rel","stylesheet"),e.setAttribute("href",s.SERVER+"script/style.css"),document.head.appendChild(e),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(){}}}}},{"../constants":3}],18:[function(t){var o=e.FrankerFaceZ,s=t("../constants"),n=t("../utils");o.ws_commands.viewers=function(e){var t=e[0],o=e[1],r=App.__container__.lookup("controller:channel"),i=r&&r.get&&r.get("id");if(i===t){var a=document.querySelector(".channel-stats .ffz.stat"),l=s.ZREKNARF+" "+n.number_commas(o);if(a)a.innerHTML=l;else{var c=document.querySelector(".channel-stats");if(!c)return;a=document.createElement("span"),a.className="ffz stat",a.title="Viewers with FrankerFaceZ",a.innerHTML=l,c.appendChild(a),jQuery(a).tipsy()}}}},{"../constants":3,"../utils":19}],19:[function(t,o){e.FrankerFaceZ,t("./constants");o.exports={update_css:function(e,t,o){var s=e.innerHTML,n="/*BEGIN "+t+"*/",r="/*END "+t+"*/",i=s.indexOf(n),a=s.indexOf(r),l=-1!==i&&-1!==a&&a>i;(l||o)&&(l&&(s=s.substr(0,i)+s.substr(a+r.length)),o&&(s+=n+o+r),e.innerHTML=s)},number_commas:function(e){var t=e.toString().split(".");return t[0]=t[0].replace(/\B(?=(\d{3})+(?!\d))/g,","),t.join(".")}}},{"./constants":3}]},{},[10]),e.ffz=new FrankerFaceZ}(window); \ No newline at end of file +!function(e){!function t(e,o,s){function n(r,i){if(!o[r]){if(!e[r]){var l="function"==typeof require&&require;if(!i&&l)return l(r,!0);if(a)return a(r,!0);throw new Error("Cannot find module '"+r+"'")}var c=o[r]={exports:{}};e[r][0].call(c.exports,function(t){var o=e[r][1][t];return n(o?o:t)},c,c.exports,t,e,o,s)}return o[r].exports}for(var a="function"==typeof require&&require,r=0;re?this._legacy_add_donors(e):void 0):void 0})},o.prototype._legacy_parse_donors=function(e){var t=0;if(null!=e)for(var o=e.trim().split(/\W+/),s=0;s))/;t.prototype.find_bttv=function(t,o){return this.has_bttv=!1,e.BTTVLOADED?this.setup_bttv():void(o>=6e4?this.log("BetterTTV was not detected after 60 seconds."):setTimeout(this.find_bttv.bind(this,t,(o||0)+t),t))},t.prototype.setup_bttv=function(){this.log("BetterTTV was detected. Hooking."),this.has_bttv=!0,this.track("setCustomVariable","3","BetterTTV",BetterTTV.info.versionString());var e=BetterTTV.chat.helpers.sendMessage,t=this;BetterTTV.chat.helpers.sendMessage=function(o){var s=o.split(" ",1)[0].toLowerCase();return"/ffz"!==s?e(o):void t.run_command(o.substr(5),BetterTTV.chat.store.currentRoom)};var s,n=BetterTTV.chat.handlers.privmsg;BetterTTV.chat.handlers.privmsg=function(e,t){s=e;var o=n(e,t);return s=null,o};var a=BetterTTV.chat.templates.privmsg;BetterTTV.chat.templates.privmsg=function(e,n,r,i,l){t.bttv_badges(l);var c=a(e,n,r,i,l);return c.replace(o,'$1 data-room="'+s+'"')};var r,i=BetterTTV.chat.templates.message;BetterTTV.chat.templates.message=function(e,t,o,s){r=e;var n=i(e,t,o,s);return r=null,n};var l=BetterTTV.chat.templates.emoticonize;BetterTTV.chat.templates.emoticonize=function(e,o){var n=l(e,o),a=t.getEmotes(r,s),o=[];return _.each(a,function(e){var s=t.emote_sets[e];s&&_.each(s.emotes,function(e){_.any(n,function(t){return _.isString(t)&&t.match(e.regex)})&&o.push(e)})}),o.length?(_.each(o,function(e){var t=[''+e.name+''],o=n;if(n=[],!o||!o.length)return n;for(var s=0;s50)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.";for(var s=t.length;t.length;){var n=t.shift();e.room.tmiRoom.sendMessage("/unmod "+n)}return"Sent unmod command for "+s+" users."},t.chat_commands.massunmod.help="Usage: /ffz massunmod \nBroadcaster only. Unmod all the users in the provided list.",t.chat_commands.massmod=function(e,t){if(t=t.join(" ").trim(),!t.length)return"You must provide a list of users to mod.";t=t.split(/\W*,\W*/);var o=this.get_user();if(!o||!o.login==e.id)return"You must be the broadcaster to use massmod.";if(t.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.";for(var s=t.length;t.length;){var n=t.shift();e.room.tmiRoom.sendMessage("/mod "+n)}return"Sent mod command for "+s+" users."},t.chat_commands.massmod.help="Usage: /ffz massmod \nBroadcaster only. Mod all the users in the provided list."},{}],4:[function(e,t){var o='',s="true"==localStorage.ffzDebugMode&&document.body.classList.contains("ffz-dev");t.exports={DEBUG:s,SERVER:s?"//localhost:8000/":"//cdn.frankerfacez.com/",SVGPATH:o,ZREKNARF:''+o+"",CHAT_BUTTON:''+o+""}},{}],5:[function(){var t=e.FrankerFaceZ;t.chat_commands.developer_mode=function(e,t){var o,t=t&&t.length?t[0].toLowerCase():null;return"y"==t||"yes"==t||"true"==t||"on"==t?o=!0:("n"==t||"no"==t||"false"==t||"off"==t)&&(o=!1),void 0===o?"Developer Mode is currently "+("true"==localStorage.ffzDebugMode?"enabled.":"disabled."):(localStorage.ffzDebugMode=o,"Developer Mode is now "+(o?"enabled":"disabled")+". Please refresh your browser.")},t.chat_commands.developer_mode.help="Usage: /ffz developer_mode \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."},{}],6:[function(){var t=e.FrankerFaceZ;t.prototype.setup_chatview=function(){this.log("Hooking the Ember Chat view.");var e=App.__container__.resolve("view:chat");this._modify_cview(e),e.create().destroy();for(var t in Ember.View.views)if(Ember.View.views.hasOwnProperty(t)){var o=Ember.View.views[t];o instanceof e&&(this.log("Adding UI link manually to Chat view.",o),o.$(".textarea-contain").append(this.build_ui_link(o)))}},t.prototype._modify_cview=function(e){var t=this;e.reopen({didInsertElement:function(){this._super(),this.$()&&this.$(".textarea-contain").append(t.build_ui_link(this))},willClearRender:function(){this._super(),this.$(".ffz-ui-toggle").remove()},ffzUpdateLink:Ember.observer("controller.currentRoom",function(){t.update_ui_link()})})}},{}],7:[function(){var t=e.FrankerFaceZ;t.prototype.setup_line=function(){this.log("Hooking the Ember Line controller.");var e=App.__container__.resolve("controller:line"),t=this;e.reopen({tokenizedMessage:function(){return t._emoticonize(this,this._super())}.property("model.message","isModeratorOrHigher","controllers.emoticons.emoticons.[]")}),this.log("Hooking the Ember Line view.");var e=App.__container__.resolve("view:line");e.reopen({didInsertElement:function(){this._super();var e=this.get("element"),o=this.get("context.model.from");e.setAttribute("data-room",this.get("context.parentController.content.id")),e.setAttribute("data-sender",o),t.render_badge(this),"false"!=localStorage.ffzCapitalize&&t.capitalize(this,o)}})},t.capitalization={},t._cap_fetching=0,t.get_capitalization=function(e,o){e=e.toLowerCase();var s=t.capitalization[e];return s&&Date.now()-s[1]<36e5?s[0]:(t._cap_fetching<5&&(t._cap_fetching++,Twitch.api.get("users/"+e).always(function(s){var n=s.display_name||e;t.capitalization[e]=[n,Date.now()],t._cap_fetching--,o&&o(n)})),s?s[0]:e)},t.prototype.capitalize=function(e,o){var s=t.get_capitalization(o,this.capitalize.bind(this,e));s&&e.$(".from").text(s)},t.chat_commands.capitalization=function(e,t){var o,t=t&&t.length?t[0].toLowerCase():null;return"y"==t||"yes"==t||"true"==t||"on"==t?o=!0:("n"==t||"no"==t||"false"==t||"off"==t)&&(o=!1),void 0===o?"Chat Name Capitalization is currently "+("false"!=localStorage.ffzCapitalize?"enabled.":"disabled."):(localStorage.ffzCapitalize=o,"Chat Name Capitalization is now "+(o?"enabled.":"disabled."))},t.chat_commands.capitalization.help="Usage: /ffz capitalization \nEnable or disable Chat Name Capitalization. This setting does not work with BetterTTV.",t.prototype._emoticonize=function(e,t){var o=e.get("parentController.model.id"),s=e.get("model.from"),n=this,a=this.getEmotes(s,o),r=[];return _.each(a,function(e){var o=n.emote_sets[e];o&&_.each(o.emotes,function(e){_.any(t,function(t){return _.isString(t)&&t.match(e.regex)})&&r.push(e)})}),r.length?("string"==typeof t&&(t=[t]),_.each(r,function(e){var o={isEmoticon:!0,cls:e.klass,emoticonSrc:e.url,altText:e.hidden?"???":e.name};t=_.compact(_.flatten(_.map(t,function(t){if(_.isObject(t))return t;var s=t.split(e.regex),n=[];return s.forEach(function(e,t){n.push(e),t!==s.length-1&&n.push(o)}),n})))}),t):t}},{}],8:[function(t){var o=e.FrankerFaceZ,s=/\.([\w\-_]+)\s*?\{content:\s*?"([^"]+)";\s*?background-image:\s*?url\("([^"]+)"\);\s*?height:\s*?(\d+)px;\s*?width:\s*?(\d+)px;\s*?margin:([^;}]+);?([^}]*)\}/gm,n=/[^\n}]*\.badges\s+\.moderator\s*{\s*background-image:\s*url\(\s*['"]([^'"]+)['"][^}]+(?:}|$)/,a=/^_([^_]+)_\d+$/,r=t("../constants"),i=t("../utils"),l=function(e){return e.moderator_badge?'.chat-line[data-room="'+e.id+'"] .badges .moderator { background-image:url("'+e.moderator_badge+'") !important; }':""};o.prototype.setup_room=function(){this.rooms={},this.log("Creating room style element.");var e=this._room_style=document.createElement("style");e.id="ffz-room-css",document.head.appendChild(e),this.log("Hooking the Ember Room model.");var t=App.__container__.resolve("model:room");this._modify_room(t);var o=t.instances;for(var s in o)if(o.hasOwnProperty(s)){var n=o[s];this.add_room(n.id,n),this._modify_room(n)}},o.chat_commands={},o.prototype.room_message=function(e,t){var o=t.split("\n");if(this.has_bttv)for(var s=0;so?this._legacy_add_room(e,t,o):void 0)})},o.prototype._legacy_load_room_css=function(e,t,o){var r=e,i=r.match(a);i&&i[1]&&(r=i[1]);var l={id:e,menu_sets:[r],sets:[r],moderator_badge:null,css:null};return o&&(o=o.replace(s,"").trim()),o&&(o=o.replace(n,function(e,t){return l.moderator_badge||"modicon.png"!==t.substr(-11)?e:(l.moderator_badge=t,"")})),l.css=o||null,this._load_room_json(e,t,l)}},{"../constants":4,"../utils":22}],9:[function(){var t=e.FrankerFaceZ;t.prototype.setup_router=function(){this.log("Hooking the Ember router.");var e=this;App.__container__.lookup("router:main").reopen({ffzTransition:function(){e.track_page()}.on("didTransition")})}},{}],10:[function(){var t=e.FrankerFaceZ;t.prototype.setup_viewers=function(){this.log("Hooking the Ember Viewers controller.");var e=App.__container__.resolve("controller:viewers");this._modify_viewers(e)},t.prototype._modify_viewers=function(e){var o=this;e.reopen({lines:function(){var e=this._super(),s=[],n={},a=null,r=App.__container__.lookup("controller:channel"),i=this.get("parentController.model.id"),l=r&&r.get("id");if(l){var c=r.get("display_name");c&&(t.capitalization[l]=[c,Date.now()])}i!=l&&(l=null);for(var u=0;uo?this._legacy_load_set(e,t,o):t&&t(!1))})},o.prototype._legacy_load_css=function(e,t,o){var n={},a={id:e,emotes:n,extra_css:null},r=this;o.replace(s,function(e,t,o,s,a,l,c,u){a=parseInt(a),l=parseInt(l),c=i(c,a);var d="."===s.substr(s.lastIndexOf("/")+1,1),h=++r._last_emote_id,f={id:h,hidden:d,name:o,height:a,width:l,url:s,margins:c,extra_css:u};return n[h]=f,""}),this._load_set_json(e,t,a)}},{"./constants":4,"./utils":22}],12:[function(t){t("./shims");var o=e.FrankerFaceZ=function(){o.instance=this,this.initialize()};o.get=function(){return o.instance};var s=o.version_info={major:3,minor:0,revision:0,toString:function(){return[s.major,s.minor,s.revision].join(".")+(s.extra||"")}};o.prototype.log=function(e,t,o){e="FFZ: "+e+(o?" -- "+JSON.stringify(t):""),void 0!==t&&console.groupCollapsed&&console.dir?(console.groupCollapsed(e),-1!==navigator.userAgent.indexOf("Firefox/")?console.log(t):console.dir(t),console.groupEnd(e)):console.log(e)},o.prototype.get_user=function(){if(e.PP&&PP.login)return PP;if(e.App){var t=App.__container__.lookup("controller:navigation");return t?t.get("userData"):void 0}},t("./socket"),t("./emoticons"),t("./badges"),t("./ember/router"),t("./ember/room"),t("./ember/line"),t("./ember/chatview"),t("./ember/viewers"),t("./tracking"),t("./debug"),t("./betterttv"),t("./featurefriday"),t("./ui/styles"),t("./ui/notifications"),t("./ui/viewer_count"),t("./ui/menu_button"),t("./ui/menu"),t("./commands"),o.prototype.initialize=function(t,o){var s=void 0!=e.App&&void 0!=App.__container__&&void 0!=App.__container__.resolve("model:room");return s?void this.setup(o):(t=t||10,void(o>=6e4?this.log('Twitch application not detected in "'+location.toString()+'". Aborting.'):setTimeout(this.initialize.bind(this,t,(o||0)+t),t)))},o.prototype.setup=function(t){var s=e.performance&&performance.now?performance.now():Date.now();this.log("Found Twitch application after "+(t||0)+' ms in "'+location+'". Initializing FrankerFaceZ version '+o.version_info),this.users={};for(var n in localStorage)"ffz_"==n.substr(0,4)&&localStorage.removeItem(n);var a=this.get_user();a&&a.name&&(o.capitalization[a.login]=[a.name,Date.now()]);try{this.ws_create(),this.setup_emoticons(),this.setup_badges(),this.setup_piwik(),this.setup_router(),this.setup_room(),this.setup_line(),this.setup_chatview(),this.setup_viewers(),this.setup_css(),this.setup_menu(),this.find_bttv(10),this.check_ff()}catch(r){return void this.log("An error occurred while starting FrankerFaceZ: "+r)}e.console&&console.time&&console.timeEnd("FrankerFaceZ Initialization");var i=e.performance&&performance.now?performance.now():Date.now(),l=i-s;this.log("Initialization complete in "+l+"ms")}},{"./badges":1,"./betterttv":2,"./commands":3,"./debug":5,"./ember/chatview":6,"./ember/line":7,"./ember/room":8,"./ember/router":9,"./ember/viewers":10,"./emoticons":11,"./featurefriday":13,"./shims":14,"./socket":15,"./tracking":16,"./ui/menu":17,"./ui/menu_button":18,"./ui/notifications":19,"./ui/styles":20,"./ui/viewer_count":21}],13:[function(t){var o=e.FrankerFaceZ,s=t("./constants");o.prototype.feature_friday=null,o.prototype.check_ff=function(e){e||this.log("Checking for Feature Friday data..."),jQuery.ajax(s.SERVER+"script/event.json",{cache:!1,dataType:"json",context:this}).done(function(e){return this._load_ff(e)}).fail(function(t){return 404==t.status?this._load_ff(null):(e=e||0,e++,10>e?setTimeout(this.check_ff.bind(this,e),250):this._load_ff(null))})},o.ws_commands.reload_ff=function(){this.check_ff()},o.prototype._feature_friday_ui=function(e,t,o){if(this.feature_friday&&this.feature_friday.channel!=e){this._emotes_for_sets(t,o,[this.feature_friday.set],"Feature Friday");var s=App.__container__.lookup("controller:channel");if(!s||s.get("id")!=this.feature_friday.channel){var n=this.feature_friday,a=this,r=document.createElement("div"),i=document.createElement("a");r.className="chat-menu-content",r.style.textAlign="center";var l=n.display_name+(n.live?" is live now!":"");i.className="button primary",i.classList.toggle("live",n.live),i.classList.toggle("blue",this.has_bttv&&BetterTTV.settings.get("showBlueButtons")),i.href="http://www.twitch.tv/"+n.channel,i.title=l,i.target="_new",i.innerHTML=""+l+"",i.addEventListener("click",function(){a.track("trackLink",this.href,"link")}),r.appendChild(i),t.appendChild(r)}}},o.prototype._load_ff=function(e){if(this.feature_friday){this.global_sets.removeObject(this.feature_friday.set);var t=this.emote_sets[this.feature_friday.set];t&&(t.global=!1),this.feature_friday=null,this.update_ui_link()}e&&e.set&&e.channel&&(this.feature_friday={set:e.set,channel:e.channel,live:!1,display_name:o.get_capitalization(e.channel,this._update_ff_name.bind(this))},this.global_sets.push(e.set),this.load_set(e.set,this._update_ff_set.bind(this)),this._update_ff_live())},o.prototype._update_ff_live=function(){if(this.feature_friday){var e=this;Twitch.api.get("streams/"+this.feature_friday.channel).done(function(t){e.feature_friday.live=null!=t.stream,e.update_ui_link()}).always(function(){e.feature_friday.timer=setTimeout(e._update_ff_live.bind(e),12e4)})}},o.prototype._update_ff_set=function(e,t){t&&(t.global=!0)},o.prototype._update_ff_name=function(e){this.feature_friday&&(this.feature_friday.display_name=e)}},{"./constants":4}],14:[function(){Array.prototype.equals=function(e){if(!e)return!1;if(this.length!=e.length)return!1;for(var t=0,o=this.length;o>t;t++)if(this[t]instanceof Array&&e[t]instanceof Array){if(!this[t].equals(e[t]))return!1}else if(this[t]!=e[t])return!1;return!0}},{}],15:[function(){var t=e.FrankerFaceZ;t.prototype._ws_open=!1,t.prototype._ws_delay=0,t.ws_commands={},t.prototype.ws_create=function(){var e=this;this._ws_last_req=0,this._ws_callbacks={};var o=this._ws_sock=new WebSocket("ws://ffz.stendec.me/");o.onopen=function(){e._ws_open=!0,e._ws_delay=0,e.log("Socket connected.");var t=e.get_user();t&&e.ws_send("setuser",t.login);for(var o in e.rooms)e.ws_send("sub",o)},o.onclose=function(){e.log("Socket closed."),e._ws_open=!1,e._ws_delay<3e4&&(e._ws_delay+=5e3),setTimeout(e.ws_create.bind(e),e._ws_delay)},o.onmessage=function(o){var s,n,a=o.data.indexOf(" "),r=o.data.substr(a+1),i=parseInt(o.data.slice(0,a));if(a=r.indexOf(" "),-1===a&&(a=r.length),s=r.slice(0,a),r=r.substr(a+1),r&&(n=JSON.parse(r)),-1===i){var l=t.ws_commands[s];l?l.bind(e)(n):e.log("Invalid command: "+s,n)}else{var c="True"===s,u=e._ws_callbacks[i];e.log("Socket Reply to "+i+" - "+(c?"SUCCESS":"FAIL"),n),u&&(delete e._ws_callbacks[i],u(c,n))}}},t.prototype.ws_send=function(e,t,o){if(!this._ws_open)return!1;var s=++this._ws_last_req;return t=void 0!==t?" "+JSON.stringify(t):"",o&&(this._ws_callbacks[s]=o),this._ws_sock.send(s+" "+e+t),s}},{}],16:[function(t){var o=e.FrankerFaceZ,s=(t("./constants"),("https:"==document.location.protocol?"https:":"http:")+"//sir.stendec.me/ffz_piwik/");o.prototype.setup_piwik=function(){if(void 0!=e._paq)return this.log("Piwik is already present. Disabling analytics."),void(this._tracking=!1);if("false"==localStorage.ffzTracking)return this.log("The user has opted out of tracking. Disabling analytics."),void(this._tracking=!1);this.log("Initializing Piwik."),this._tracking=!0;var t=e._paq=[];t.push(["setSiteId",1]),t.push(["setTrackerUrl",s+"piwik.php"]),this.has_bttv&&t.push(["setCustomVariable","3","BetterTTV",BetterTTV.info.versionString()]);var o=this.get_user(),n=this;o?(t.push(["setCustomVariable","1","Partnered",o.is_partner?"Yes":"No"]),t.push(["setCustomVariable","2","User Type",o.is_staff?"Staff":o.is_admin?"Admin":"User"]),t.push(["setUserId",o.login]),Twitch.api.get("channels/"+o.login).done(function(e){e.logo&&n.track("setCustomVariable","4","Avatar",e.logo)}).always(function(){n.track_page()})):this.track_page(),"true"==localStorage.ffzTracking&&(this.track("trackEvent","Analytics","Enable"),localStorage.removeItem("ffzTracking"));var a=document.createElement("script");a.type="text/javascript",a.defer=!0,a.async=!0,a.src=s+"piwik.js",document.head.appendChild(a)},o.chat_commands.analytics=function(e,t){var o,t=t&&t.length?t[0].toLowerCase():null;return"y"==t||"yes"==t||"true"==t||"on"==t?o=!0:("n"==t||"no"==t||"false"==t||"off"==t)&&(o=!1),void 0===o?"Analytics are currently "+("false"!=localStorage.ffzTracking?"enabled.":"disabled."):(this._tracking&&!o&&"false"!=localStorage.ffzTracking&&this.track("trackEvent","Analytics","Disable"),localStorage.ffzTracking=o,"Analytics are now "+(o?"enabled":"disabled")+". Please refresh your browser.")},o.chat_commands.analytics.help="Usage: /ffz analytics \nEnable or disable FrankerFaceZ analytics. We collect some data about your browser and how you use FrankerFaceZ to help us improve the script. Turn off analytics if you'd rather we not.",o.prototype.track=function(){this._tracking&&e._paq&&_paq.push(Array.prototype.slice.call(arguments))},o.prototype.track_page=function(){if(this._tracking){this._old_url&&this.track("setReferrerUrl",this._old_url),this._old_url=document.location.toString(),this.track("setCustomUrl",this._old_url),this.track("deleteCustomVariable","1","page"),this.track("deleteCustomVariable","3","page");var e=App.__container__.resolve("router:main").router.currentHandlerInfos;if(e&&0!=e.length){var t=e[e.length-1];if("channel.index"==t.name&&t.context){var o=t.context.get("isFollowing.isFollowing");void 0!==o&&null!==o&&this.track("setCustomVariable","1","Following",o?"Yes":"No","page");var s=t.context.get("game");s&&this.track("setCustomVariable","3","Game",s,"page"),this.track("trackPageView",document.title)}}}}},{"./constants":4}],17:[function(){var t=e.FrankerFaceZ;t.prototype.setup_menu=function(){this.log("Installing mouse-up event to auto-close menus.");var e=this;jQuery(document).mouseup(function(t){var o,s=e._popup;s&&(s=jQuery(s),o=s.parent(),o.is(t.target)||0!==o.has(t.target).length||(s.remove(),delete e._popup))})},t.prototype.build_ui_popup=function(e){var t=this._popup;if(t)return t.parentElement.removeChild(t),void delete this._popup;var o=document.createElement("div"),s=document.createElement("div");o.className="emoticon-selector chat-menu ffz-ui-popup",s.className="emoticon-selector-box dropmenu",o.appendChild(s);var n=e.get("controller.currentRoom.id"),a=this.rooms[n];this.log("Menu for Room: "+n,a),this.track("trackEvent","Menu","Open",n);var r=document.createElement("a");r.className="button glyph-only ffz-button",r.title="Advertise for FrankerFaceZ in chat!",r.href="#",r.innerHTML='';var i=document.createElement("div");i.className="list-header first",i.appendChild(r),i.appendChild(document.createTextNode("FrankerFaceZ")),s.appendChild(i);var l=this._emotes_for_sets(s,e,a&&a.menu_sets||[]);0===l?r.addEventListener("click",this._add_emote.bind(this,e,"To use custom emoticons in tons of channels, get FrankerFaceZ from http://www.frankerfacez.com")):r.addEventListener("click",this._add_emote.bind(this,e,"To view this channel's emoticons, get FrankerFaceZ from http://www.frankerfacez.com")),this._feature_friday_ui(n,s,e),this._popup=o,s.style.maxHeight=Math.max(300,e.$().height()-171)+"px",e.$(".chat-interface").append(o)},t.prototype._emotes_for_sets=function(e,t,o,s,n){if(null!=s){var a=document.createElement("div");a.className="list-header",a.appendChild(document.createTextNode(s)),n&&a.appendChild(n),e.appendChild(a)}var r=document.createElement("div"),i=0;r.className="emoticon-grid"; +for(var l=0;l0){n=!0;break}}e.classList.toggle("no-emotes",!n),e.classList.toggle("live",i),e.classList.toggle("dark",a),e.classList.toggle("blue",r)}},{"../constants":4}],19:[function(){var t=e.FrankerFaceZ;t.prototype.show_notification=function(t){e.noty({text:t,theme:"ffzTheme",layout:"bottomCenter",closeWith:["button"]}).show()},t.ws_commands.message=function(e){this.show_notification(e)}},{}],20:[function(t){var o=e.FrankerFaceZ,s=t("../constants");o.prototype.setup_css=function(){this.log("Injecting main FrankerFaceZ CSS.");var e=this._main_style=document.createElement("link");e.id="ffz-ui-css",e.setAttribute("rel","stylesheet"),e.setAttribute("href",s.SERVER+"script/style.css"),document.head.appendChild(e),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(){}}}}},{"../constants":4}],21:[function(t){var o=e.FrankerFaceZ,s=t("../constants"),n=t("../utils");o.ws_commands.viewers=function(e){var t=e[0],o=e[1],a=App.__container__.lookup("controller:channel"),r=a&&a.get&&a.get("id");if(r===t){var i=document.querySelector(".channel-stats .ffz.stat"),l=s.ZREKNARF+" "+n.number_commas(o);if(i)i.innerHTML=l;else{var c=document.querySelector(".channel-stats");if(!c)return;i=document.createElement("span"),i.className="ffz stat",i.title="Viewers with FrankerFaceZ",i.innerHTML=l,c.appendChild(i),jQuery(i).tipsy()}}}},{"../constants":4,"../utils":22}],22:[function(t,o){e.FrankerFaceZ,t("./constants");o.exports={update_css:function(e,t,o){var s=e.innerHTML,n="/*BEGIN "+t+"*/",a="/*END "+t+"*/",r=s.indexOf(n),i=s.indexOf(a),l=-1!==r&&-1!==i&&i>r;(l||o)&&(l&&(s=s.substr(0,r)+s.substr(i+a.length)),o&&(s+=n+o+a),e.innerHTML=s)},number_commas:function(e){var t=e.toString().split(".");return t[0]=t[0].replace(/\B(?=(\d{3})+(?!\d))/g,","),t.join(".")}}},{"./constants":4}]},{},[12]),e.ffz=new FrankerFaceZ}(window); \ No newline at end of file diff --git a/src/betterttv.js b/src/betterttv.js index d342bcaf..bf492794 100644 --- a/src/betterttv.js +++ b/src/betterttv.js @@ -23,6 +23,7 @@ FFZ.prototype.setup_bttv = function() { this.log("BetterTTV was detected. Hooking."); this.has_bttv = true; + this.track('setCustomVariable', '3', 'BetterTTV', BetterTTV.info.versionString()); // Send Message Behavior var original_send = BetterTTV.chat.helpers.sendMessage, f = this; @@ -73,11 +74,7 @@ FFZ.prototype.setup_bttv = function() { var original_emoticonize = BetterTTV.chat.templates.emoticonize; BetterTTV.chat.templates.emoticonize = function(message, emotes) { var tokens = original_emoticonize(message, emotes), - user = f.users[received_sender], - room = f.rooms[received_room]; - - // Get our sets. - var sets = _.union(user && user.sets || [], room && room.sets || [], f.global_sets), + sets = f.getEmotes(received_sender, received_room), emotes = []; // Build a list of emotes that match. diff --git a/src/commands.js b/src/commands.js new file mode 100644 index 00000000..bdac82fc --- /dev/null +++ b/src/commands.js @@ -0,0 +1,59 @@ +var FFZ = window.FrankerFaceZ; + +// ----------------- +// Mass Unmod +// ----------------- + +FFZ.chat_commands.massunmod = function(room, args) { + args = args.join(" ").trim(); + + if ( ! args.length ) + return "You must provide a list of users to unmod."; + + args = args.split(/\W*,\W*/); + + var user = this.get_user(); + if ( ! user || ! user.login == room.id ) + return "You must be the broadcaster to use massunmod."; + + if ( args.length > 50 ) + return "Each user you unmod counts as a single message. To avoid being globally banned, please limit yourself to 50 at a time and wait between uses."; + + var count = args.length; + while(args.length) { + var name = args.shift(); + room.room.tmiRoom.sendMessage("/unmod " + name); + } + + return "Sent unmod command for " + count + " users."; +} + +FFZ.chat_commands.massunmod.help = "Usage: /ffz massunmod \nBroadcaster only. Unmod all the users in the provided list."; + + +FFZ.chat_commands.massmod = function(room, args) { + args = args.join(" ").trim(); + + if ( ! args.length ) + return "You must provide a list of users to mod."; + + args = args.split(/\W*,\W*/); + + var user = this.get_user(); + if ( ! user || ! user.login == room.id ) + return "You must be the broadcaster to use massmod."; + + if ( args.length > 50 ) + return "Each user you mod counts as a single message. To avoid being globally banned, please limit yourself to 50 at a time and wait between uses."; + + + var count = args.length; + while(args.length) { + var name = args.shift(); + room.room.tmiRoom.sendMessage("/mod " + name); + } + + return "Sent mod command for " + count + " users."; +} + +FFZ.chat_commands.massmod.help = "Usage: /ffz massmod \nBroadcaster only. Mod all the users in the provided list."; \ No newline at end of file diff --git a/src/ember/line.js b/src/ember/line.js index 83d0a4e0..ce52e6b6 100644 --- a/src/ember/line.js +++ b/src/ember/line.js @@ -105,12 +105,10 @@ FFZ.chat_commands.capitalization.help = "Usage: /ffz capitalization \nEn FFZ.prototype._emoticonize = function(controller, tokens) { var room_id = controller.get("parentController.model.id"), user_id = controller.get("model.from"), - user = this.users[user_id], - room = this.rooms[room_id], f = this; // Get our sets. - var sets = _.union(user && user.sets || [], room && room.sets || [], f.global_sets), + var sets = this.getEmotes(user_id, room_id), emotes = []; // Build a list of emotes that match. diff --git a/src/ember/room.js b/src/ember/room.js index a07786e4..c4f227c6 100644 --- a/src/ember/room.js +++ b/src/ember/room.js @@ -70,8 +70,14 @@ FFZ.prototype.run_command = function(text, room_id) { if ( ! room || !room.room ) return; - if ( ! text ) + 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(); diff --git a/src/ember/router.js b/src/ember/router.js new file mode 100644 index 00000000..03cb808f --- /dev/null +++ b/src/ember/router.js @@ -0,0 +1,17 @@ +var FFZ = window.FrankerFaceZ; + + +// -------------------- +// Initialization +// -------------------- + +FFZ.prototype.setup_router = function() { + this.log("Hooking the Ember router."); + + var f = this; + App.__container__.lookup('router:main').reopen({ + ffzTransition: function() { + f.track_page(); + }.on('didTransition') + }); +} \ No newline at end of file diff --git a/src/emoticons.js b/src/emoticons.js index 4646f8c0..d393fbda 100644 --- a/src/emoticons.js +++ b/src/emoticons.js @@ -1,31 +1,55 @@ 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, constants = require('./constants'), - utils = require('./utils'); + utils = require('./utils'), -var loaded_global = function(set_id, success, data) { - if ( ! success ) - return; + loaded_global = function(set_id, success, data) { + if ( ! success ) + return; - data.global = true; - this.global_sets.push(set_id); -} + 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; -var 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; + 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"; + }, - return margins; -} + 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."); @@ -44,12 +68,31 @@ FFZ.prototype.setup_emoticons = function() { } +// --------------------- +// 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); } @@ -73,24 +116,6 @@ FFZ.prototype.unload_set = function(set_id) { } -var 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"; -} - -var 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"; -} - -var build_css = build_new_css; - - - FFZ.prototype._load_set_json = function(set_id, callback, data) { // Store our set. this.emote_sets[set_id] = data; diff --git a/src/featurefriday.js b/src/featurefriday.js index b03067aa..3ab417e2 100644 --- a/src/featurefriday.js +++ b/src/featurefriday.js @@ -17,21 +17,19 @@ FFZ.prototype.check_ff = function(tries) { if ( ! tries ) this.log("Checking for Feature Friday data..."); - var f = this; - jQuery.getJSON(constants.SERVER + "script/event.json") + jQuery.ajax(constants.SERVER + "script/event.json", {cache: false, dataType: "json", context: this}) .done(function(data) { - return f._load_ff(data); - + return this._load_ff(data); }).fail(function(data) { if ( data.status == 404 ) - return f._load_ff(null); + return this._load_ff(null); tries = tries || 0; tries++; if ( tries < 10 ) - return setTimeout(f.check_ff.bind(this, tries), 250); + return setTimeout(this.check_ff.bind(this, tries), 250); - return f._load_ff(null); + return this._load_ff(null); }); } @@ -58,7 +56,7 @@ FFZ.prototype._feature_friday_ui = function(room_id, parent, view) { return; - var ff = this.feature_friday, + var ff = this.feature_friday, f = this, btnc = document.createElement('div'), btn = document.createElement('a'); @@ -76,6 +74,9 @@ FFZ.prototype._feature_friday_ui = function(room_id, parent, view) { btn.target = "_new"; btn.innerHTML = "" + message + ""; + // Track the number of users to click this button. + btn.addEventListener('click', function() { f.track('trackLink', this.href, 'link'); }); + btnc.appendChild(btn); parent.appendChild(btnc); } diff --git a/src/main.js b/src/main.js index 19ab9c4b..2615fb4b 100644 --- a/src/main.js +++ b/src/main.js @@ -65,11 +65,14 @@ require('./socket'); require('./emoticons'); require('./badges'); +require('./ember/router'); require('./ember/room'); require('./ember/line'); require('./ember/chatview'); require('./ember/viewers'); +require('./tracking'); + require('./debug'); require('./betterttv'); @@ -83,6 +86,8 @@ require('./ui/viewer_count'); require('./ui/menu_button'); require('./ui/menu'); +require('./commands'); + // --------------- // Initialization @@ -136,6 +141,9 @@ FFZ.prototype.setup = function(delay) { this.setup_emoticons(); this.setup_badges(); + this.setup_piwik(); + + this.setup_router(); this.setup_room(); this.setup_line(); this.setup_chatview(); diff --git a/src/tracking.js b/src/tracking.js new file mode 100644 index 00000000..a9748417 --- /dev/null +++ b/src/tracking.js @@ -0,0 +1,131 @@ +var FFZ = window.FrankerFaceZ, + constants = require('./constants'), + PIWIK = ("https:" == document.location.protocol ? 'https:' : 'http:') + '//sir.stendec.me/ffz_piwik/'; + + +// -------------------- +// Initialization +// -------------------- + +FFZ.prototype.setup_piwik = function() { + if ( window._paq != undefined ) { + this.log("Piwik is already present. Disabling analytics."); + this._tracking = false; + return; + } + + if ( localStorage['ffzTracking'] == "false" ) { + this.log("The user has opted out of tracking. Disabling analytics."); + this._tracking = false; + return; + } + + this.log("Initializing Piwik."); + this._tracking = true; + var _paq = window._paq = []; + + _paq.push(['setSiteId', 1]); + _paq.push(['setTrackerUrl', PIWIK + 'piwik.php']); + + if ( this.has_bttv ) + _paq.push(['setCustomVariable', '3', 'BetterTTV', BetterTTV.info.versionString()]); + + var user = this.get_user(), f = this; + if ( user ) { + _paq.push(['setCustomVariable', '1', 'Partnered', user.is_partner ? "Yes" : "No"]) + _paq.push(['setCustomVariable', '2', 'User Type', user.is_staff ? "Staff" : (user.is_admin ? "Admin" : "User")]); + _paq.push(['setUserId', user.login]); + + Twitch.api.get("channels/" + user.login) + .done(function(data) { + if ( data.logo ) + f.track('setCustomVariable', '4', 'Avatar', data.logo); + }).always(function() { f.track_page(); }); + + } else + this.track_page(); + + // If someone turned analytics back ON, track that. + if ( localStorage['ffzTracking'] == "true" ) { + this.track('trackEvent', 'Analytics', 'Enable'); + localStorage.removeItem('ffzTracking'); + } + + var script = document.createElement('script'); + script.type = 'text/javascript'; + script.defer = true; + script.async = true; + script.src = PIWIK + 'piwik.js'; + document.head.appendChild(script); +} + + +// -------------------- +// Command +// -------------------- + +FFZ.chat_commands.analytics = function(room, args) { + var enabled, args = args && args.length ? args[0].toLowerCase() : null; + if ( args == "y" || args == "yes" || args == "true" || args == "on" ) + enabled = true; + else if ( args == "n" || args == "no" || args == "false" || args == "off" ) + enabled = false; + + if ( enabled === undefined ) + return "Analytics are currently " + (localStorage.ffzTracking != "false" ? "enabled." : "disabled."); + + // Track that someone turned off analytics. + if ( this._tracking && ! enabled && localStorage.ffzTracking != "false" ) + this.track('trackEvent', 'Analytics', 'Disable'); + + localStorage.ffzTracking = enabled; + + return "Analytics are now " + (enabled ? "enabled" : "disabled") + ". Please refresh your browser."; +} + +FFZ.chat_commands.analytics.help = "Usage: /ffz analytics \nEnable or disable FrankerFaceZ analytics. We collect some data about your browser and how you use FrankerFaceZ to help us improve the script. Turn off analytics if you'd rather we not."; + + + +// -------------------- +// Tracking Helpers +// -------------------- + +FFZ.prototype.track = function() { + if ( ! this._tracking ) + return; + + window._paq && _paq.push(Array.prototype.slice.call(arguments)); +} + + +FFZ.prototype.track_page = function() { + if ( ! this._tracking ) + return; + + if ( this._old_url ) + this.track('setReferrerUrl', this._old_url); + + this._old_url = document.location.toString(); + this.track('setCustomUrl', this._old_url); + + this.track('deleteCustomVariable', '1', 'page'); + this.track('deleteCustomVariable', '3', 'page'); + + var routes = App.__container__.resolve('router:main').router.currentHandlerInfos; + if ( ! routes || routes.length == 0 ) + return; + + var last = routes[routes.length - 1]; + if ( last.name == "channel.index" && last.context ) { + var following = last.context.get("isFollowing.isFollowing"); + if ( following !== undefined && following !== null ) + this.track('setCustomVariable', '1', 'Following', (following ? "Yes" : "No"), 'page'); + + var game = last.context.get("game"); + if ( game ) + this.track("setCustomVariable", "3", "Game", game, "page"); + + this.track("trackPageView", document.title); + } +} \ No newline at end of file diff --git a/src/ui/menu.js b/src/ui/menu.js index 1a6b64ea..ab37e6d8 100644 --- a/src/ui/menu.js +++ b/src/ui/menu.js @@ -50,6 +50,7 @@ FFZ.prototype.build_ui_popup = function(view) { room = this.rooms[room_id]; this.log("Menu for Room: " + room_id, room); + this.track('trackEvent', 'Menu', 'Open', room_id); // Add the header and ad button. var btn = document.createElement('a');