diff --git a/script.js b/script.js
index 5210f324..dcdc0a34 100644
--- a/script.js
+++ b/script.js
@@ -35,6 +35,62 @@ var badge_css = function(badge) {
// Render Badge
// --------------------
+FFZ.prototype.bttv_badges = function(data) {
+ var user_id = data.sender,
+ user = this.users[user_id],
+ badges_out = [],
+ insert_at = -1;
+
+ if ( ! user || ! user.badges )
+ return;
+
+ // Determine where in the list to insert these badges.
+ for(var i=0; i < data.badges.length; i++) {
+ var badge = data.badges[i];
+ if ( badge.type == "subscriber" || badge.type == "turbo" ) {
+ insert_at = i;
+ break;
+ }
+ }
+
+
+ for (var slot in user.badges) {
+ if ( ! user.badges.hasOwnProperty(slot) )
+ continue;
+
+ var badge = user.badges[slot],
+ full_badge = this.badges[badge.id] || {},
+ desc = badge.title || full_badge.title,
+ style = "",
+ alpha = BetterTTV.settings.get('alphaTags');
+
+ if ( badge.image )
+ style += 'background-image: url(\\"' + badge.image + '\\"); ';
+
+ if ( badge.color && ! alpha )
+ style += 'background-color: ' + badge.color + '; ';
+
+ if ( badge.extra_css )
+ style += badge.extra_css;
+
+ if ( style )
+ desc += '" style="' + style;
+
+ badges_out.push([(insert_at == -1 ? 1 : -1) * slot, {type: "ffz-badge-" + badge.id + (alpha ? " alpha" : ""), name: "", description: desc}]);
+ }
+
+ badges_out.sort(function(a,b){return a[0] - b[0]});
+
+ if ( insert_at == -1 ) {
+ while(badges_out.length)
+ data.badges.push(badges_out.shift()[1]);
+ } else {
+ while(badges_out.length)
+ data.badges.insertAt(insert_at, badges_out.shift()[1]);
+ }
+}
+
+
FFZ.prototype.render_badge = function(view) {
var user = view.get('context.model.from'),
room_id = view.get('context.parentController.content.id'),
@@ -134,9 +190,142 @@ FFZ.prototype._legacy_parse_donors = function(data) {
this.log("Added donor badge to " + utils.number_commas(count) + " users.");
}
-},{"./constants":2,"./utils":16}],2:[function(require,module,exports){
+},{"./constants":3,"./utils":17}],2:[function(require,module,exports){
+var FFZ = window.FrankerFaceZ,
+ SENDER_REGEX = /(\sdata-sender="[^"]*"(?=>))/;
+
+
+// --------------------
+// Initialization
+// --------------------
+
+FFZ.prototype.find_bttv = function(increment, delay) {
+ this.has_bttv = false;
+ if ( window.BTTVLOADED )
+ return this.setup_bttv();
+
+ if ( delay >= 60000 )
+ this.log("BetterTTV was not detected after 60 seconds.");
+ else
+ setTimeout(this.find_bttv.bind(this, increment, (delay||0) + increment),
+ increment);
+}
+
+
+FFZ.prototype.setup_bttv = function() {
+ this.log("BetterTTV was detected. Hooking.");
+ this.has_bttv = true;
+
+
+ // Send Message Behavior
+ var original_send = BetterTTV.chat.helpers.sendMessage, f = this;
+ BetterTTV.chat.helpers.sendMessage = function(message) {
+ var cmd = message.split(' ', 1)[0].toLowerCase();
+
+ if ( cmd === "/ffz" )
+ f.run_command(message.substr(5), BetterTTV.chat.store.currentRoom);
+ else
+ return original_send(message);
+ }
+
+
+ // Ugly Hack for Current Room
+ var original_handler = BetterTTV.chat.handlers.privmsg,
+ received_room;
+ BetterTTV.chat.handlers.privmsg = function(room, data) {
+ received_room = room;
+ var output = original_handler(room, data);
+ received_room = null;
+ return output;
+ }
+
+
+ // Message Display Behavior
+ var original_privmsg = BetterTTV.chat.templates.privmsg;
+ BetterTTV.chat.templates.privmsg = function(highlight, action, server, isMod, data) {
+ // Handle badges.
+ f.bttv_badges(data);
+
+ var output = original_privmsg(highlight, action, server, isMod, data);
+ return output.replace(SENDER_REGEX, '$1 data-room="' + received_room + '"');
+ }
+
+
+ // Ugly Hack for Current Sender
+ var original_template = BetterTTV.chat.templates.message,
+ received_sender;
+ BetterTTV.chat.templates.message = function(sender, message, emotes, colored) {
+ received_sender = sender;
+ var output = original_template(sender, message, emotes, colored);
+ received_sender = null;
+ return output;
+ }
+
+
+ // Emoticonize
+ var original_emoticonize = BetterTTV.chat.templates.emoticonize;
+ BetterTTV.chat.templates.emoticonize = function(message, emotes) {
+ var tokens = original_emoticonize(message, emotes),
+ 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),
+ emotes = [];
+
+ // Build a list of emotes that match.
+ _.each(sets, function(set_id) {
+ var set = f.emote_sets[set_id];
+ if ( ! set )
+ return;
+
+ _.each(set.emotes, function(emote) {
+ _.any(tokens, function(token) {
+ return _.isString(token) && token.match(emote.regex);
+ }) && emotes.push(emote);
+ });
+ });
+
+ // Don't bother proceeding if we have no emotes.
+ if ( ! emotes.length )
+ return tokens;
+
+ // Why is emote parsing so bad? ;_;
+ _.each(emotes, function(emote) {
+ var eo = ['
'],
+ old_tokens = tokens;
+
+ tokens = [];
+
+ if ( ! old_tokens || ! old_tokens.length )
+ return tokens;
+
+ for(var i=0; i < old_tokens.length; i++) {
+ var token = old_tokens[i];
+ if ( typeof token != "string" ) {
+ tokens.push(token);
+ continue;
+ }
+
+ var tbits = token.split(emote.regex);
+ tbits.forEach(function(val, ind) {
+ if ( val && val.length )
+ tokens.push(val);
+
+ if ( ind !== tbits.length - 1 )
+ tokens.push(eo);
+ });
+ }
+ });
+
+ return tokens;
+ }
+
+ this.update_ui_link();
+}
+},{}],3:[function(require,module,exports){
var SVGPATH = '',
- DEBUG = localStorage.ffzDebugMode == "true";
+ DEBUG = localStorage.ffzDebugMode == "true" && document.body.classList.contains('ffz-dev');
module.exports = {
DEBUG: DEBUG,
@@ -146,15 +335,15 @@ module.exports = {
ZREKNARF: '',
CHAT_BUTTON: ''
}
-},{}],3:[function(require,module,exports){
+},{}],4:[function(require,module,exports){
var FFZ = window.FrankerFaceZ;
-// --------------------
-// Debug Command
-// --------------------
+// -----------------------
+// Developer Mode Command
+// -----------------------
-FFZ.chat_commands.debug = function(room, args) {
+FFZ.chat_commands.developer_mode = function(room, args) {
var enabled, args = args && args.length ? args[0].toLowerCase() : null;
if ( args == "y" || args == "yes" || args == "true" || args == "on" )
enabled = true;
@@ -162,14 +351,15 @@ FFZ.chat_commands.debug = function(room, args) {
enabled = false;
if ( enabled === undefined )
- enabled = !(localStorage.ffzDebugMode == "true");
+ return "Developer Mode is currently " + (localStorage.ffzDebugMode == "true" ? "enabled." : "disabled.");
localStorage.ffzDebugMode = enabled;
- return "Debug Mode is now " + (enabled ? "enabled" : "disabled") + ". Please refresh your browser.";
+ return "Developer Mode is now " + (enabled ? "enabled" : "disabled") + ". Please refresh your browser.";
}
-FFZ.chat_commands.debug.help = "Usage: /ffz debug [on|off]\nEnable or disable Debug Mode. When Debug Mode is enabled, the script will be reloaded from //localhost:8000/script.js instead of from the CDN.";
-},{}],4:[function(require,module,exports){
+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){
var FFZ = window.FrankerFaceZ;
@@ -225,7 +415,7 @@ FFZ.prototype._modify_cview = function(view) {
})
});
}
-},{}],5:[function(require,module,exports){
+},{}],6:[function(require,module,exports){
var FFZ = window.FrankerFaceZ;
@@ -242,8 +432,6 @@ FFZ.prototype.setup_line = function() {
Line.reopen({
tokenizedMessage: function() {
// Add our own step to the tokenization procedure.
- var tokens = f._emoticonize(this, this._super());
- f.log("Chat Tokens", tokens);
return f._emoticonize(this, this._super());
}.property("model.message", "isModeratorOrHigher", "controllers.emoticons.emoticons.[]")
@@ -258,16 +446,45 @@ FFZ.prototype.setup_line = function() {
didInsertElement: function() {
this._super();
- var el = this.get('element');
+ var el = this.get('element'),
+ user = this.get('context.model.from');
+
el.setAttribute('data-room', this.get('context.parentController.content.id'));
- el.setAttribute('data-sender', this.get('context.model.from'));
+ el.setAttribute('data-sender', user);
f.render_badge(this);
+ f.capitalize(this, user);
+
}
});
}
+// ---------------------
+// Capitalization
+// ---------------------
+
+FFZ.capitalization = {};
+
+FFZ.prototype.capitalize = function(view, user) {
+ if ( FFZ.capitalization[user] )
+ return view.$('.from').text(FFZ.capitalization[user]);
+
+ var f = this;
+ jQuery.getJSON("https://api.twitch.tv/kraken/channels/" + user + "?callback=?")
+ .always(function(data) {
+ if ( data.display_name == undefined )
+ FFZ.capitalization[user] = user;
+ else
+ FFZ.capitalization[user] = data.display_name;
+
+ f.capitalize(view, user);
+ });
+}
+
+
+
+
// ---------------------
// Emoticon Replacement
// ---------------------
@@ -311,7 +528,7 @@ FFZ.prototype._emoticonize = function(controller, tokens) {
// emoticon.
_.each(emotes, function(emote) {
//var eo = {isEmoticon:true, cls: emote.klass};
- var eo = {emoticonSrc: emote.url, altText: emote.name};
+ var eo = {isEmoticon:true, cls: emote.klass, emoticonSrc: emote.url, altText: emote.name};
tokens = _.compact(_.flatten(_.map(tokens, function(token) {
if ( _.isObject(token) )
@@ -329,7 +546,7 @@ FFZ.prototype._emoticonize = function(controller, tokens) {
return tokens;
}
-},{}],6:[function(require,module,exports){
+},{}],7:[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*['"]([^'"]+)['"][^}]+(?:}|$)/,
@@ -386,8 +603,14 @@ FFZ.chat_commands = {};
FFZ.prototype.room_message = function(room, text) {
var lines = text.split("\n");
- for(var i=0; i < lines.length; i++)
- room.room.addMessage({style: 'ffz admin', from: 'FFZ', message: lines[i]});
+ if ( this.has_bttv ) {
+ for(var i=0; i < lines.length; i++)
+ BetterTTV.chat.handlers.onPrivmsg(room.id, {style: 'admin', date: new Date(), from: 'jtv', message: lines[i]});
+
+ } else {
+ for(var i=0; i < lines.length; i++)
+ room.room.addMessage({style: 'ffz admin', date: new Date(), from: 'FFZ', message: lines[i]});
+ }
}
@@ -524,22 +747,6 @@ FFZ.prototype._load_room_json = function(room_id, callback, data) {
}
-/*FFZ.ws_commands.sets_for_room = function(data) {
- var room = this.rooms[data.room];
- if ( ! room )
- return;
-
- for(var i=0; i < data.sets.length; i++) {
- var set = data.sets[i];
- if ( room.sets.contains(set) )
- continue;
-
- room.sets.push(set);
- this.load_set(set);
- }
-}*/
-
-
// --------------------
// Ember Modifications
// --------------------
@@ -615,7 +822,7 @@ 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":2,"../utils":16}],7:[function(require,module,exports){
+},{"../constants":3,"../utils":17}],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,
constants = require('./constants'),
@@ -698,13 +905,15 @@ var build_legacy_css = function(emote) {
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_css = function(emote) {
+var build_new_css = function(emote) {
if ( ! emote.margins && ! emote.extra_css )
- return "";
+ return build_legacy_css(emote);
- return 'img[src="' + emote.url + '"] { ' + (emote.margins ? "margin: " + emote.margins + ";" : "") + (emote.extra_css || "") + " }\n";
+ 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) {
@@ -712,6 +921,7 @@ FFZ.prototype._load_set_json = function(set_id, callback, data) {
this.emote_sets[set_id] = data;
data.users = [];
data.global = false;
+ data.count = 0;
// Iterate through all the emoticons, building CSS and regex objects as appropriate.
var output_css = "";
@@ -729,10 +939,12 @@ FFZ.prototype._load_set_json = function(set_id, callback, data) {
emote.regex = new RegExp("\\b" + emote.name + "\\b", "g");
output_css += build_css(emote);
+ data.count++;
}
utils.update_css(this._emote_style, set_id, output_css + (data.extra_css || ""));
this.log("Updated emoticons for set: " + set_id, data);
+ this.update_ui_link();
if ( callback )
callback(true, data);
@@ -774,7 +986,7 @@ FFZ.prototype._legacy_load_css = function(set_id, callback, data) {
this._load_set_json(set_id, callback, output);
}
-},{"./constants":2,"./utils":16}],8:[function(require,module,exports){
+},{"./constants":3,"./utils":17}],9:[function(require,module,exports){
// Modify Array and others.
require('./shims');
@@ -844,6 +1056,8 @@ require('./ember/chatview');
require('./debug');
+require('./betterttv');
+
require('./ui/styles');
require('./ui/notifications');
require('./ui/viewer_count');
@@ -851,6 +1065,7 @@ require('./ui/viewer_count');
require('./ui/menu_button');
require('./ui/menu');
+
// ---------------
// Initialization
// ---------------
@@ -896,6 +1111,9 @@ FFZ.prototype.setup = function(delay) {
this.setup_css();
this.setup_menu();
+ this.find_bttv(10);
+
+
} catch(err) {
this.log("An error occurred while starting FrankerFaceZ: " + err);
return;
@@ -903,7 +1121,7 @@ FFZ.prototype.setup = function(delay) {
this.log("Initialization complete.");
}
-},{"./badges":1,"./debug":3,"./ember/chatview":4,"./ember/line":5,"./ember/room":6,"./emoticons":7,"./shims":9,"./socket":10,"./ui/menu":11,"./ui/menu_button":12,"./ui/notifications":13,"./ui/styles":14,"./ui/viewer_count":15}],9:[function(require,module,exports){
+},{"./badges":1,"./betterttv":2,"./debug":4,"./ember/chatview":5,"./ember/line":6,"./ember/room":7,"./emoticons":8,"./shims":10,"./socket":11,"./ui/menu":12,"./ui/menu_button":13,"./ui/notifications":14,"./ui/styles":15,"./ui/viewer_count":16}],10:[function(require,module,exports){
Array.prototype.equals = function (array) {
// if the other array is a falsy value, return
if (!array)
@@ -929,7 +1147,7 @@ Array.prototype.equals = function (array) {
}
-},{}],10:[function(require,module,exports){
+},{}],11:[function(require,module,exports){
var FFZ = window.FrankerFaceZ;
FFZ.prototype._ws_open = false;
@@ -1023,7 +1241,7 @@ FFZ.prototype.ws_send = function(func, data, callback) {
this._ws_sock.send(request + " " + func + data);
return request;
}
-},{}],11:[function(require,module,exports){
+},{}],12:[function(require,module,exports){
var FFZ = window.FrankerFaceZ;
@@ -1126,15 +1344,20 @@ FFZ.prototype._emotes_for_sets = function(parent, view, sets, header, btn) {
for(var i=0; i < sets.length; i++) {
var set = this.emote_sets[sets[i]];
+ if ( ! set || ! set.emotes )
+ continue;
+
for(var eid in set.emotes) {
var emote = set.emotes[eid];
if ( !set.emotes.hasOwnProperty(eid) || emote.hidden )
continue;
c++;
- var s = document.createElement('img');
- s.src = emote.url;
- //s.className = 'emoticon ' + emote.klass + ' tooltip';
+ var s = document.createElement('span');
+ s.className = 'emoticon tooltip';
+ s.style.backgroundImage = 'url("' + emote.url + '")';
+ s.style.width = emote.width + "px";
+ s.style.height = emote.height + "px";
s.title = emote.name;
s.addEventListener('click', this._add_emote.bind(this, view, emote.name));
grid.appendChild(s);
@@ -1159,7 +1382,7 @@ FFZ.prototype._add_emote = function(view, emote) {
room.set('messageToSend', current_text + (emote.name || emote));
}
-},{}],12:[function(require,module,exports){
+},{}],13:[function(require,module,exports){
var FFZ = window.FrankerFaceZ,
constants = require('../constants');
@@ -1168,8 +1391,6 @@ var FFZ = window.FrankerFaceZ,
// --------------------
FFZ.prototype.build_ui_link = function(view) {
- // TODO: Detect dark mode from BTTV.
-
var link = document.createElement('a');
link.className = 'ffz-ui-toggle';
link.innerHTML = constants.CHAT_BUTTON;
@@ -1184,18 +1405,32 @@ FFZ.prototype.build_ui_link = function(view) {
FFZ.prototype.update_ui_link = function(link) {
var controller = App.__container__.lookup('controller:chat');
link = link || document.querySelector('a.ffz-ui-toggle');
- if ( !link || !controller ) return;
+ if ( !link || !controller ) return this.log("No button.");
var room_id = controller.get('currentRoom.id'),
room = this.rooms[room_id],
- has_emotes = room && room.sets.length > 0;
+ has_emotes = false,
- if ( has_emotes )
- link.classList.remove('no-emotes');
- else
- link.classList.add('no-emotes');
+ dark = (this.has_bttv ? BetterTTV.settings.get('darkenedMode') : false),
+ blue = (this.has_bttv ? BetterTTV.settings.get('showBlueButtons') : false);
+
+
+ // Check for emoticons.
+ if ( room && room.sets.length ) {
+ for(var i=0; i < room.sets.length; i++) {
+ var set = this.emote_sets[room.sets[i]];
+ if ( set && set.count > 0 ) {
+ has_emotes = true;
+ break;
+ }
+ }
+ }
+
+ link.classList.toggle('no-emotes', ! has_emotes);
+ link.classList.toggle('dark', dark);
+ link.classList.toggle('blue', blue);
}
-},{"../constants":2}],13:[function(require,module,exports){
+},{"../constants":3}],14:[function(require,module,exports){
var FFZ = window.FrankerFaceZ;
FFZ.prototype.show_notification = function(message) {
@@ -1211,7 +1446,7 @@ FFZ.prototype.show_notification = function(message) {
FFZ.ws_commands.message = function(message) {
this.show_notification(message);
}
-},{}],14:[function(require,module,exports){
+},{}],15:[function(require,module,exports){
var FFZ = window.FrankerFaceZ,
constants = require('../constants');
@@ -1236,7 +1471,7 @@ FFZ.prototype.setup_css = function() {
}
};
}
-},{"../constants":2}],15:[function(require,module,exports){
+},{"../constants":3}],16:[function(require,module,exports){
var FFZ = window.FrankerFaceZ,
constants = require('../constants'),
utils = require('../utils');
@@ -1273,7 +1508,7 @@ FFZ.ws_commands.viewers = function(data) {
jQuery(view_count).tipsy();
}
}
-},{"../constants":2,"../utils":16}],16:[function(require,module,exports){
+},{"../constants":3,"../utils":17}],17:[function(require,module,exports){
var FFZ = window.FrankerFaceZ,
constants = require('./constants');
@@ -1304,4 +1539,4 @@ module.exports = {
return parts.join(".");
}
}
-},{"./constants":2}]},{},[8]);window.ffz = new FrankerFaceZ()}(window));
\ No newline at end of file
+},{"./constants":3}]},{},[9]);window.ffz = new FrankerFaceZ()}(window));
\ No newline at end of file
diff --git a/script.min.js b/script.min.js
index 308d2138..f73cb984 100644
--- a/script.min.js
+++ b/script.min.js
@@ -1 +1 @@
-!function(e){!function t(e,o,s){function n(i,a){if(!o[i]){if(!e[i]){var c="function"==typeof require&&require;if(!a&&c)return c(i,!0);if(r)return r(i,!0);throw new Error("Cannot find module '"+i+"'")}var l=o[i]={exports:{}};e[i][0].call(l.exports,function(t){var o=e[i][1][t];return n(o?o:t)},l,l.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'+o+"",CHAT_BUTTON:'"}},{}],3:[function(){var t=e.FrankerFaceZ;t.chat_commands.debug=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&&(o=!("true"==localStorage.ffzDebugMode)),localStorage.ffzDebugMode=o,"Debug Mode is now "+(o?"enabled":"disabled")+". Please refresh your browser."},t.chat_commands.debug.help="Usage: /ffz debug [on|off]\nEnable or disable Debug Mode. When Debug Mode is enabled, the script will be reloaded from //localhost:8000/script.js instead of from the CDN."},{}],4:[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()})})}},{}],5:[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(){var e=t._emoticonize(this,this._super());return t.log("Chat Tokens",e),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");e.setAttribute("data-room",this.get("context.parentController.content.id")),e.setAttribute("data-sender",this.get("context.model.from")),t.render_badge(this)}})},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),c=[];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)})&&c.push(e)})}),c.length?("string"==typeof t&&(t=[t]),_.each(c,function(e){var o={emoticonSrc:e.url,altText: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}},{}],6:[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"),c=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){for(var o=t.split("\n"),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 c={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 c.moderator_badge||"modicon.png"!==t.substr(-11)?e:(c.moderator_badge=t,"")})),c.css=o||null,this._load_room_json(e,t,c)}},{"../constants":2,"../utils":16}],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=t("./constants"),r=t("./utils"),i=function(e,t,o){t&&(o.global=!0,this.global_sets.push(e))},a=function(e,t){var o=e.split(/ +/);return 2!=o.length?e:(o[0]=parseFloat(o[0]),o[1]=parseFloat(o[1]),o[0]==(t-18)/-2&&0==o[1]?null:e)};o.prototype.setup_emoticons=function(){this.log("Preparing emoticon system."),this.emote_sets={},this.global_sets=[],this._last_emote_id=0,this.log("Creating emoticon style element.");var e=this._emote_style=document.createElement("style");e.id="ffz-emoticon-css",document.head.appendChild(e),this.log("Loading global emote set."),this.load_set("global",i.bind(this,"global"))},o.ws_commands.reload_set=function(e){this.load_set(e)},o.prototype.load_set=function(e,t){return this._legacy_load_set(e,t)},o.prototype.unload_set=function(e){var t=this.emote_sets[e];if(t){this.log("Unloading emoticons for set: "+e),r.update_css(this._emote_style,e,null),delete this.emote_sets[e];for(var o=0;oo?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,c,l,d){r=parseInt(r),c=parseInt(c),l=a(l,r);var u="."===s.substr(s.lastIndexOf("/")+1,1),m=++i._last_emote_id,h={id:m,hidden:u,name:o,height:r,width:c,url:s,margins:l,extra_css:d};return n[m]=h,""}),this._load_set_json(e,t,r)}},{"./constants":2,"./utils":16}],8:[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),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("./debug"),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(e){this.log("Found Twitch application after "+(e||0)+' ms in "'+location+'". Initializing FrankerFaceZ version '+o.version_info),this.users={};try{this.ws_create(),this.setup_emoticons(),this.setup_badges(),this.setup_room(),this.setup_line(),this.setup_chatview(),this.setup_css(),this.setup_menu()}catch(t){return void this.log("An error occurred while starting FrankerFaceZ: "+t)}this.log("Initialization complete.")}},{"./badges":1,"./debug":3,"./ember/chatview":4,"./ember/line":5,"./ember/room":6,"./emoticons":7,"./shims":9,"./socket":10,"./ui/menu":11,"./ui/menu_button":12,"./ui/notifications":13,"./ui/styles":14,"./ui/viewer_count":15}],9:[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}},{}],10:[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 c=t.ws_commands[s];c?c.bind(e)(n):e.log("Invalid command: "+s,n)}else{var l="True"===s,d=e._ws_callbacks[a];e.log("Socket Reply to "+a+" - "+(l?"SUCCESS":"FAIL"),n),d&&(delete e._ws_callbacks[a],d(l,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}},{}],11:[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 c=this._emotes_for_sets(s,e,r&&r.menu_sets||[]);0===c?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._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 c=0;c0;n?e.classList.remove("no-emotes"):e.classList.add("no-emotes")}}},{"../constants":2}],13:[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)}},{}],14:[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":2}],15:[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"),c=s.ZREKNARF+" "+n.number_commas(o);if(a)a.innerHTML=c;else{var l=document.querySelector(".channel-stats");if(!l)return;a=document.createElement("span"),a.className="ffz stat",a.title="Viewers with FrankerFaceZ",a.innerHTML=c,l.appendChild(a),jQuery(a).tipsy()}}}},{"../constants":2,"../utils":16}],16:[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),c=-1!==i&&-1!==a&&a>i;(c||o)&&(c&&(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":2}]},{},[8]),e.ffz=new FrankerFaceZ}(window);
\ No newline at end of file
+!function(e){!function t(e,o,s){function n(i,a){if(!o[i]){if(!e[i]){var c="function"==typeof require&&require;if(!a&&c)return c(i,!0);if(r)return r(i,!0);throw new Error("Cannot find module '"+i+"'")}var l=o[i]={exports:{}};e[i][0].call(l.exports,function(t){var o=e[i][1][t];return n(o?o:t)},l,l.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,c){t.bttv_badges(c);var l=r(e,n,i,a,c);return l.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 c=BetterTTV.chat.templates.emoticonize;BetterTTV.chat.templates.emoticonize=function(e,o){var n=c(e,o),r=t.users[i],a=t.rooms[s],l=_.union(r&&r.sets||[],a&&a.sets||[],t.global_sets),o=[];return _.each(l,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=['
'],o=n;if(n=[],!o||!o.length)return n;for(var s=0;s'+o+"",CHAT_BUTTON:'"}},{}],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),t.capitalize(this,o)}})},t.capitalization={},t.prototype.capitalize=function(e,o){if(t.capitalization[o])return e.$(".from").text(t.capitalization[o]);var s=this;jQuery.getJSON("https://api.twitch.tv/kraken/channels/"+o+"?callback=?").always(function(n){t.capitalization[o]=void 0==n.display_name?o:n.display_name,s.capitalize(e,o)})},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),c=[];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)})&&c.push(e)})}),c.length?("string"==typeof t&&(t=[t]),_.each(c,function(e){var o={isEmoticon:!0,cls:e.klass,emoticonSrc:e.url,altText: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"),c=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 c={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 c.moderator_badge||"modicon.png"!==t.substr(-11)?e:(c.moderator_badge=t,"")})),c.css=o||null,this._load_room_json(e,t,c)}},{"../constants":3,"../utils":17}],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=t("./constants"),r=t("./utils"),i=function(e,t,o){t&&(o.global=!0,this.global_sets.push(e))},a=function(e,t){var o=e.split(/ +/);return 2!=o.length?e:(o[0]=parseFloat(o[0]),o[1]=parseFloat(o[1]),o[0]==(t-18)/-2&&0==o[1]?null:e)};o.prototype.setup_emoticons=function(){this.log("Preparing emoticon system."),this.emote_sets={},this.global_sets=[],this._last_emote_id=0,this.log("Creating emoticon style element.");var e=this._emote_style=document.createElement("style");e.id="ffz-emoticon-css",document.head.appendChild(e),this.log("Loading global emote set."),this.load_set("global",i.bind(this,"global"))},o.ws_commands.reload_set=function(e){this.load_set(e)},o.prototype.load_set=function(e,t){return this._legacy_load_set(e,t)},o.prototype.unload_set=function(e){var t=this.emote_sets[e];if(t){this.log("Unloading emoticons for set: "+e),r.update_css(this._emote_style,e,null),delete this.emote_sets[e];for(var o=0;oo?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,c,l,d){r=parseInt(r),c=parseInt(c),l=a(l,r);var u="."===s.substr(s.lastIndexOf("/")+1,1),h=++i._last_emote_id,m={id:h,hidden:u,name:o,height:r,width:c,url:s,margins:l,extra_css:d};return n[h]=m,""}),this._load_set_json(e,t,r)}},{"./constants":3,"./utils":17}],9:[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),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("./debug"),t("./betterttv"),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(e){this.log("Found Twitch application after "+(e||0)+' ms in "'+location+'". Initializing FrankerFaceZ version '+o.version_info),this.users={};try{this.ws_create(),this.setup_emoticons(),this.setup_badges(),this.setup_room(),this.setup_line(),this.setup_chatview(),this.setup_css(),this.setup_menu(),this.find_bttv(10)}catch(t){return void this.log("An error occurred while starting FrankerFaceZ: "+t)}this.log("Initialization complete.")}},{"./badges":1,"./betterttv":2,"./debug":4,"./ember/chatview":5,"./ember/line":6,"./ember/room":7,"./emoticons":8,"./shims":10,"./socket":11,"./ui/menu":12,"./ui/menu_button":13,"./ui/notifications":14,"./ui/styles":15,"./ui/viewer_count":16}],10:[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}},{}],11:[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 c=t.ws_commands[s];c?c.bind(e)(n):e.log("Invalid command: "+s,n)}else{var l="True"===s,d=e._ws_callbacks[a];e.log("Socket Reply to "+a+" - "+(l?"SUCCESS":"FAIL"),n),d&&(delete e._ws_callbacks[a],d(l,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}},{}],12:[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 c=this._emotes_for_sets(s,e,r&&r.menu_sets||[]);0===c?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._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 c=0;c0){n=!0;break}}e.classList.toggle("no-emotes",!n),e.classList.toggle("dark",r),e.classList.toggle("blue",i)}},{"../constants":3}],14:[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)}},{}],15:[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}],16:[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"),c=s.ZREKNARF+" "+n.number_commas(o);if(a)a.innerHTML=c;else{var l=document.querySelector(".channel-stats");if(!l)return;a=document.createElement("span"),a.className="ffz stat",a.title="Viewers with FrankerFaceZ",a.innerHTML=c,l.appendChild(a),jQuery(a).tipsy()}}}},{"../constants":3,"../utils":17}],17:[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),c=-1!==i&&-1!==a&&a>i;(c||o)&&(c&&(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}]},{},[9]),e.ffz=new FrankerFaceZ}(window);
\ No newline at end of file
diff --git a/src/badges.js b/src/badges.js
index 114b69e5..814ff962 100644
--- a/src/badges.js
+++ b/src/badges.js
@@ -34,6 +34,62 @@ var badge_css = function(badge) {
// Render Badge
// --------------------
+FFZ.prototype.bttv_badges = function(data) {
+ var user_id = data.sender,
+ user = this.users[user_id],
+ badges_out = [],
+ insert_at = -1;
+
+ if ( ! user || ! user.badges )
+ return;
+
+ // Determine where in the list to insert these badges.
+ for(var i=0; i < data.badges.length; i++) {
+ var badge = data.badges[i];
+ if ( badge.type == "subscriber" || badge.type == "turbo" ) {
+ insert_at = i;
+ break;
+ }
+ }
+
+
+ for (var slot in user.badges) {
+ if ( ! user.badges.hasOwnProperty(slot) )
+ continue;
+
+ var badge = user.badges[slot],
+ full_badge = this.badges[badge.id] || {},
+ desc = badge.title || full_badge.title,
+ style = "",
+ alpha = BetterTTV.settings.get('alphaTags');
+
+ if ( badge.image )
+ style += 'background-image: url(\\"' + badge.image + '\\"); ';
+
+ if ( badge.color && ! alpha )
+ style += 'background-color: ' + badge.color + '; ';
+
+ if ( badge.extra_css )
+ style += badge.extra_css;
+
+ if ( style )
+ desc += '" style="' + style;
+
+ badges_out.push([(insert_at == -1 ? 1 : -1) * slot, {type: "ffz-badge-" + badge.id + (alpha ? " alpha" : ""), name: "", description: desc}]);
+ }
+
+ badges_out.sort(function(a,b){return a[0] - b[0]});
+
+ if ( insert_at == -1 ) {
+ while(badges_out.length)
+ data.badges.push(badges_out.shift()[1]);
+ } else {
+ while(badges_out.length)
+ data.badges.insertAt(insert_at, badges_out.shift()[1]);
+ }
+}
+
+
FFZ.prototype.render_badge = function(view) {
var user = view.get('context.model.from'),
room_id = view.get('context.parentController.content.id'),
diff --git a/src/betterttv.js b/src/betterttv.js
new file mode 100644
index 00000000..bd5b1581
--- /dev/null
+++ b/src/betterttv.js
@@ -0,0 +1,132 @@
+var FFZ = window.FrankerFaceZ,
+ SENDER_REGEX = /(\sdata-sender="[^"]*"(?=>))/;
+
+
+// --------------------
+// Initialization
+// --------------------
+
+FFZ.prototype.find_bttv = function(increment, delay) {
+ this.has_bttv = false;
+ if ( window.BTTVLOADED )
+ return this.setup_bttv();
+
+ if ( delay >= 60000 )
+ this.log("BetterTTV was not detected after 60 seconds.");
+ else
+ setTimeout(this.find_bttv.bind(this, increment, (delay||0) + increment),
+ increment);
+}
+
+
+FFZ.prototype.setup_bttv = function() {
+ this.log("BetterTTV was detected. Hooking.");
+ this.has_bttv = true;
+
+
+ // Send Message Behavior
+ var original_send = BetterTTV.chat.helpers.sendMessage, f = this;
+ BetterTTV.chat.helpers.sendMessage = function(message) {
+ var cmd = message.split(' ', 1)[0].toLowerCase();
+
+ if ( cmd === "/ffz" )
+ f.run_command(message.substr(5), BetterTTV.chat.store.currentRoom);
+ else
+ return original_send(message);
+ }
+
+
+ // Ugly Hack for Current Room
+ var original_handler = BetterTTV.chat.handlers.privmsg,
+ received_room;
+ BetterTTV.chat.handlers.privmsg = function(room, data) {
+ received_room = room;
+ var output = original_handler(room, data);
+ received_room = null;
+ return output;
+ }
+
+
+ // Message Display Behavior
+ var original_privmsg = BetterTTV.chat.templates.privmsg;
+ BetterTTV.chat.templates.privmsg = function(highlight, action, server, isMod, data) {
+ // Handle badges.
+ f.bttv_badges(data);
+
+ var output = original_privmsg(highlight, action, server, isMod, data);
+ return output.replace(SENDER_REGEX, '$1 data-room="' + received_room + '"');
+ }
+
+
+ // Ugly Hack for Current Sender
+ var original_template = BetterTTV.chat.templates.message,
+ received_sender;
+ BetterTTV.chat.templates.message = function(sender, message, emotes, colored) {
+ received_sender = sender;
+ var output = original_template(sender, message, emotes, colored);
+ received_sender = null;
+ return output;
+ }
+
+
+ // Emoticonize
+ var original_emoticonize = BetterTTV.chat.templates.emoticonize;
+ BetterTTV.chat.templates.emoticonize = function(message, emotes) {
+ var tokens = original_emoticonize(message, emotes),
+ 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),
+ emotes = [];
+
+ // Build a list of emotes that match.
+ _.each(sets, function(set_id) {
+ var set = f.emote_sets[set_id];
+ if ( ! set )
+ return;
+
+ _.each(set.emotes, function(emote) {
+ _.any(tokens, function(token) {
+ return _.isString(token) && token.match(emote.regex);
+ }) && emotes.push(emote);
+ });
+ });
+
+ // Don't bother proceeding if we have no emotes.
+ if ( ! emotes.length )
+ return tokens;
+
+ // Why is emote parsing so bad? ;_;
+ _.each(emotes, function(emote) {
+ var eo = ['
'],
+ old_tokens = tokens;
+
+ tokens = [];
+
+ if ( ! old_tokens || ! old_tokens.length )
+ return tokens;
+
+ for(var i=0; i < old_tokens.length; i++) {
+ var token = old_tokens[i];
+ if ( typeof token != "string" ) {
+ tokens.push(token);
+ continue;
+ }
+
+ var tbits = token.split(emote.regex);
+ tbits.forEach(function(val, ind) {
+ if ( val && val.length )
+ tokens.push(val);
+
+ if ( ind !== tbits.length - 1 )
+ tokens.push(eo);
+ });
+ }
+ });
+
+ return tokens;
+ }
+
+ this.update_ui_link();
+}
\ No newline at end of file
diff --git a/src/ember/line.js b/src/ember/line.js
index 73d66eb4..208896ca 100644
--- a/src/ember/line.js
+++ b/src/ember/line.js
@@ -14,8 +14,6 @@ FFZ.prototype.setup_line = function() {
Line.reopen({
tokenizedMessage: function() {
// Add our own step to the tokenization procedure.
- var tokens = f._emoticonize(this, this._super());
- f.log("Chat Tokens", tokens);
return f._emoticonize(this, this._super());
}.property("model.message", "isModeratorOrHigher", "controllers.emoticons.emoticons.[]")
@@ -30,16 +28,45 @@ FFZ.prototype.setup_line = function() {
didInsertElement: function() {
this._super();
- var el = this.get('element');
+ var el = this.get('element'),
+ user = this.get('context.model.from');
+
el.setAttribute('data-room', this.get('context.parentController.content.id'));
- el.setAttribute('data-sender', this.get('context.model.from'));
+ el.setAttribute('data-sender', user);
f.render_badge(this);
+ f.capitalize(this, user);
+
}
});
}
+// ---------------------
+// Capitalization
+// ---------------------
+
+FFZ.capitalization = {};
+
+FFZ.prototype.capitalize = function(view, user) {
+ if ( FFZ.capitalization[user] )
+ return view.$('.from').text(FFZ.capitalization[user]);
+
+ var f = this;
+ jQuery.getJSON("https://api.twitch.tv/kraken/channels/" + user + "?callback=?")
+ .always(function(data) {
+ if ( data.display_name == undefined )
+ FFZ.capitalization[user] = user;
+ else
+ FFZ.capitalization[user] = data.display_name;
+
+ f.capitalize(view, user);
+ });
+}
+
+
+
+
// ---------------------
// Emoticon Replacement
// ---------------------
@@ -83,7 +110,7 @@ FFZ.prototype._emoticonize = function(controller, tokens) {
// emoticon.
_.each(emotes, function(emote) {
//var eo = {isEmoticon:true, cls: emote.klass};
- var eo = {emoticonSrc: emote.url, altText: emote.name};
+ var eo = {isEmoticon:true, cls: emote.klass, emoticonSrc: emote.url, altText: emote.name};
tokens = _.compact(_.flatten(_.map(tokens, function(token) {
if ( _.isObject(token) )
diff --git a/src/ember/room.js b/src/ember/room.js
index 4a702d76..a07786e4 100644
--- a/src/ember/room.js
+++ b/src/ember/room.js
@@ -54,8 +54,14 @@ FFZ.chat_commands = {};
FFZ.prototype.room_message = function(room, text) {
var lines = text.split("\n");
- for(var i=0; i < lines.length; i++)
- room.room.addMessage({style: 'ffz admin', from: 'FFZ', message: lines[i]});
+ if ( this.has_bttv ) {
+ for(var i=0; i < lines.length; i++)
+ BetterTTV.chat.handlers.onPrivmsg(room.id, {style: 'admin', date: new Date(), from: 'jtv', message: lines[i]});
+
+ } else {
+ for(var i=0; i < lines.length; i++)
+ room.room.addMessage({style: 'ffz admin', date: new Date(), from: 'FFZ', message: lines[i]});
+ }
}
@@ -192,22 +198,6 @@ FFZ.prototype._load_room_json = function(room_id, callback, data) {
}
-/*FFZ.ws_commands.sets_for_room = function(data) {
- var room = this.rooms[data.room];
- if ( ! room )
- return;
-
- for(var i=0; i < data.sets.length; i++) {
- var set = data.sets[i];
- if ( room.sets.contains(set) )
- continue;
-
- room.sets.push(set);
- this.load_set(set);
- }
-}*/
-
-
// --------------------
// Ember Modifications
// --------------------
diff --git a/src/emoticons.js b/src/emoticons.js
index ab9698a2..4646f8c0 100644
--- a/src/emoticons.js
+++ b/src/emoticons.js
@@ -80,13 +80,15 @@ var build_legacy_css = function(emote) {
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_css = function(emote) {
+var build_new_css = function(emote) {
if ( ! emote.margins && ! emote.extra_css )
- return "";
+ return build_legacy_css(emote);
- return 'img[src="' + emote.url + '"] { ' + (emote.margins ? "margin: " + emote.margins + ";" : "") + (emote.extra_css || "") + " }\n";
+ 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) {
@@ -94,6 +96,7 @@ FFZ.prototype._load_set_json = function(set_id, callback, data) {
this.emote_sets[set_id] = data;
data.users = [];
data.global = false;
+ data.count = 0;
// Iterate through all the emoticons, building CSS and regex objects as appropriate.
var output_css = "";
@@ -111,10 +114,12 @@ FFZ.prototype._load_set_json = function(set_id, callback, data) {
emote.regex = new RegExp("\\b" + emote.name + "\\b", "g");
output_css += build_css(emote);
+ data.count++;
}
utils.update_css(this._emote_style, set_id, output_css + (data.extra_css || ""));
this.log("Updated emoticons for set: " + set_id, data);
+ this.update_ui_link();
if ( callback )
callback(true, data);
diff --git a/src/main.js b/src/main.js
index 9028c3e7..5edbe102 100644
--- a/src/main.js
+++ b/src/main.js
@@ -67,6 +67,8 @@ require('./ember/chatview');
require('./debug');
+require('./betterttv');
+
require('./ui/styles');
require('./ui/notifications');
require('./ui/viewer_count');
@@ -74,6 +76,7 @@ require('./ui/viewer_count');
require('./ui/menu_button');
require('./ui/menu');
+
// ---------------
// Initialization
// ---------------
@@ -119,6 +122,9 @@ FFZ.prototype.setup = function(delay) {
this.setup_css();
this.setup_menu();
+ this.find_bttv(10);
+
+
} catch(err) {
this.log("An error occurred while starting FrankerFaceZ: " + err);
return;
diff --git a/src/ui/menu.js b/src/ui/menu.js
index e88c5534..1fdaf2eb 100644
--- a/src/ui/menu.js
+++ b/src/ui/menu.js
@@ -100,15 +100,20 @@ FFZ.prototype._emotes_for_sets = function(parent, view, sets, header, btn) {
for(var i=0; i < sets.length; i++) {
var set = this.emote_sets[sets[i]];
+ if ( ! set || ! set.emotes )
+ continue;
+
for(var eid in set.emotes) {
var emote = set.emotes[eid];
if ( !set.emotes.hasOwnProperty(eid) || emote.hidden )
continue;
c++;
- var s = document.createElement('img');
- s.src = emote.url;
- //s.className = 'emoticon ' + emote.klass + ' tooltip';
+ var s = document.createElement('span');
+ s.className = 'emoticon tooltip';
+ s.style.backgroundImage = 'url("' + emote.url + '")';
+ s.style.width = emote.width + "px";
+ s.style.height = emote.height + "px";
s.title = emote.name;
s.addEventListener('click', this._add_emote.bind(this, view, emote.name));
grid.appendChild(s);
diff --git a/src/ui/menu_button.js b/src/ui/menu_button.js
index 573eb94e..b2b847bd 100644
--- a/src/ui/menu_button.js
+++ b/src/ui/menu_button.js
@@ -6,8 +6,6 @@ var FFZ = window.FrankerFaceZ,
// --------------------
FFZ.prototype.build_ui_link = function(view) {
- // TODO: Detect dark mode from BTTV.
-
var link = document.createElement('a');
link.className = 'ffz-ui-toggle';
link.innerHTML = constants.CHAT_BUTTON;
@@ -22,14 +20,28 @@ FFZ.prototype.build_ui_link = function(view) {
FFZ.prototype.update_ui_link = function(link) {
var controller = App.__container__.lookup('controller:chat');
link = link || document.querySelector('a.ffz-ui-toggle');
- if ( !link || !controller ) return;
+ if ( !link || !controller ) return this.log("No button.");
var room_id = controller.get('currentRoom.id'),
room = this.rooms[room_id],
- has_emotes = room && room.sets.length > 0;
+ has_emotes = false,
- if ( has_emotes )
- link.classList.remove('no-emotes');
- else
- link.classList.add('no-emotes');
+ dark = (this.has_bttv ? BetterTTV.settings.get('darkenedMode') : false),
+ blue = (this.has_bttv ? BetterTTV.settings.get('showBlueButtons') : false);
+
+
+ // Check for emoticons.
+ if ( room && room.sets.length ) {
+ for(var i=0; i < room.sets.length; i++) {
+ var set = this.emote_sets[room.sets[i]];
+ if ( set && set.count > 0 ) {
+ has_emotes = true;
+ break;
+ }
+ }
+ }
+
+ link.classList.toggle('no-emotes', ! has_emotes);
+ link.classList.toggle('dark', dark);
+ link.classList.toggle('blue', blue);
}
\ No newline at end of file
diff --git a/test/server.js b/test/server.js
index f20fdde6..3bc0d929 100644
--- a/test/server.js
+++ b/test/server.js
@@ -1,3 +1,5 @@
+var version = "0.1.0";
+
var fs = require("fs"),
http = require("http"),
path = require("path"),
@@ -8,6 +10,12 @@ http.createServer(function(req, res) {
var uri = url.parse(req.url).pathname,
lpath = path.join(uri).split(path.sep);
+ if ( uri == "/dev_server" ) {
+ console.log("[200] GET " + uri);
+ res.writeHead(200, {"Content-Type": "application/json", "Access-Control-Allow-Origin": "*"});
+ return res.end(JSON.stringify({path: process.cwd(), version: version}));
+ }
+
if ( ! lpath[0] )
lpath.shift();
@@ -26,7 +34,7 @@ http.createServer(function(req, res) {
if ( fs.lstatSync(file).isDirectory() ) {
console.log("[403] GET " + uri);
- res.writeHead(403);
+ res.writeHead(403, {"Access-Control-Allow-Origin": "*"});
res.write('403 Forbidden');
return res.end();
}