var FFZ = window.FrankerFaceZ,
constants = require('../constants'),
utils = require('../utils'),
SENDER_REGEX = /(\sdata-sender="[^"]*"(?=>))/,
HOP = Object.prototype.hasOwnProperty;
// --------------------
// Initialization
// --------------------
FFZ.prototype.find_bttv = function(increment, delay) {
this.has_bttv = false;
if ( window.BTTVLOADED )
return this.setup_bttv(delay||0);
if ( delay >= 60000 )
this.log("BetterTTV was not detected after 60 seconds.");
else
setTimeout(this.find_bttv.bind(this, increment, (delay||0) + increment),
increment);
}
FFZ.prototype.setup_bttv = function(delay) {
this.log("BetterTTV was detected after " + delay + "ms. Hooking.");
this.has_bttv = true;
// Disable Dark if it's enabled.
document.body.classList.remove("ffz-dark");
if ( this._dark_style ) {
this._dark_style.parentElement.removeChild(this._dark_style);
this._dark_style = undefined;
}
if ( this._layout_style ) {
this._layout_style.parentElement.removeChild(this._layout_style);
this._layout_style = undefined;
}
if ( this._chat_style ) {
utils.update_css(this._chat_style, 'chat_font_size', '');
utils.update_css(this._chat_style, 'chat_ts_font_size', '');
}
// Remove Sub Count and the Chart
if ( this.is_dashboard ) {
this._update_subscribers();
this._remove_dash_chart();
}
document.body.classList.add('ffz-bttv');
var last_dark = BetterTTV.settings.get('darkenedMode');
document.body.classList.toggle('ffz-bttv-dark', last_dark);
setInterval(function() {
var new_dark = BetterTTV.settings.get('darkenedMode');
if ( new_dark !== last_dark ) {
document.body.classList.toggle('ffz-bttv-dark', new_dark);
last_dark = new_dark;
}
}, 500);
// Disable Chat Tabs
if ( this._chatv ) {
if ( this.settings.group_tabs )
this._chatv.ffzDisableTabs();
this._chatv.ffzTeardownMenu();
this._chatv.ffzUnloadHost();
}
if ( this._roomv ) {
// Disable Chat Pause
if ( this.settings.chat_hover_pause )
this._roomv.ffzDisableFreeze();
// And hide the status
if ( this.settings.room_status )
this._roomv.ffzUpdateStatus();
}
this.disconnect_extra_chat();
// Disable style blocks.
this.toggle_style('chat-setup');
this.toggle_style('chat-padding');
this.toggle_style('chat-background');
this.toggle_style('chat-separator');
this.toggle_style('chat-separator-3d');
this.toggle_style('chat-separator-3d-inset');
this.toggle_style('chat-separator-wide');
this.toggle_style('chat-colors-gray');
this.toggle_style('badges-rounded');
this.toggle_style('badges-circular');
this.toggle_style('badges-blank');
this.toggle_style('badges-circular-small');
this.toggle_style('badges-transparent');
this.toggle_style('badges-sub-notice');
this.toggle_style('badges-sub-notice-on');
// Disable other features too.
var cl = document.body.classList;
cl.remove('ffz-transparent-badges');
cl.remove("ffz-sidebar-swap");
cl.remove("ffz-portrait");
cl.remove("ffz-minimal-channel-title");
cl.remove("ffz-flip-dashboard");
// Remove Following Count
if ( this.settings.following_count ) {
this._schedule_following_count();
this._draw_following_count();
this._draw_following_channels();
}
// Send Message Behavior
var f = this,
BC = BetterTTV.chat,
original_send = BC.helpers.sendMessage;
BC.helpers.sendMessage = function(message) {
var cmd = message.split(' ', 1)[0].toLowerCase();
if ( cmd === '/ffz' )
f.run_ffz_command(message.substr(5), BC.store.currentRoom);
else
return original_send(message);
}
// Ugly Hack for Current Room, as this is stripped out before we get to
// the actual privmsg renderer.
var original_handler = BC.handlers.onPrivmsg,
received_room;
BC.handlers.onPrivmsg = function(room, data) {
received_room = room;
var output = original_handler(room, data);
received_room = null;
return output;
}
// Message Display Behavior
var original_privmsg = BC.templates.privmsg;
BC.templates.privmsg = function(data, opts) {
try {
opts = opts || {};
// Handle badges.
f.bttv_badges(data);
// Now, do everything else manually because things are hard-coded.
return '
'+
BC.templates.timestamp(data.time)+' '+
(opts.isMod ? BC.templates.modicons():'')+' '+
BC.templates.badges(data.badges)+
BC.templates.from(data.nickname, data.color)+
BC.templates.message(data.sender, data.message, {
emotes: data.emotes,
colored: (opts.action && !opts.highlight) ? data.color : false,
bits: data.bits
}) +
'
';
} catch(err) {
f.log("Error: ", err);
return original_privmsg(data, opts);
}
}
// Whispers too!
var original_whisper = BC.templates.whisper;
BC.templates.whisper = function(data) {
try {
// Handle badges.
f.bttv_badges(data);
// Now, do everything else manually because things are hard-coded.
return '' +
BC.templates.timestamp(data.time) + ' ' +
(data.badges && data.badges.length ? BC.templates.badges(data.badges) : '') +
BC.templates.whisperName(data.sender, data.receiver, data.from, data.to, data.fromColor, data.toColor) +
BC.templates.message(data.sender, data.message, {
emotes: data.emotes,
colored: false
}) +
'
';
} catch(err) {
f.log("Error: ", err);
return original_whisper(data);
}
}
// Message Renderer. I had to completely rewrite this method to get it to
// use my replacement emoticonizer.
var original_message = BC.templates.message,
received_sender;
BC.templates.message = function(sender, message, data) {
try {
var colored = data.colored || false,
force = data.force || false,
emotes = data.emotes,
rawMessage = encodeURIComponent(message);
if(sender !== 'jtv') {
// Hackilly send our state across.
received_sender = sender;
var tokenizedMessage = BC.templates.emoticonize(message, emotes);
received_sender = null;
for(var i=0; i' + message + '';
} catch(err) {
f.log("Error: ", err);
return original_message(sender, message, emotes, colored);
}
};
// Emoji!
var parse_emoji = function(token) {
var setting = f.settings.parse_emoji,
output = [],
segments = token.split(constants.EMOJI_REGEX),
text = null;
while(segments.length) {
text = (text || '') + segments.shift();
if ( segments.length ) {
var match = segments.shift(),
eid = utils.emoji_to_codepoint(match),
data = f.emoji_data[eid],
src = data && (setting === 3 ? data.one_src : (setting === 2 ? data.noto_src : data.tw_src));
if ( src ) {
if ( text && text.length )
output.push(text);
// We still want to use a special token even if emoji display is disabled
// as, otherwise, BTTV will render the emoji itself, which the user has no
// way of disabling if not for this.
if ( setting === 0 )
output.push([data.raw]);
else {
var code = utils.quote_attr(data.raw);
output.push(['
']);
}
text = null;
} else
text = (text || '') + match;
}
}
if ( text && text.length )
output.push(text);
return output;
}
// Emoticonize
var original_emoticonize = BC.templates.emoticonize;
BC.templates.emoticonize = function(message, emotes) {
var tokens = original_emoticonize(message, emotes),
room = (received_room || BetterTTV.getChannel()),
l_room = room && room.toLowerCase(),
l_sender = received_sender && received_sender.toLowerCase(),
sets = f.getEmotes(l_sender, l_room),
emotes = {}, emote,
user = f.get_user(),
new_tokens = [],
mine = user && user.login === l_sender;
// Build an object with all of our emotes.
for(var i=0; i < sets.length; i++) {
var emote_set = f.emote_sets[sets[i]];
if ( emote_set && emote_set.emoticons )
for(var emote_id in emote_set.emoticons) {
emote = emote_set.emoticons[emote_id];
if ( ! HOP.call(emotes, emote.name) )
emotes[emote.name] = emote;
}
}
for(var i=0, l=tokens.length; i < l; i++) {
var token = tokens[i];
if ( typeof token !== "string" ) {
new_tokens.push(token);
continue;
}
// Split the token!
var segments = token.split(' '),
text = [], segment;
for(var x=0,y=segments.length; x < y; x++) {
segment = segments[x];
if ( HOP.call(emotes, segment) ) {
emote = emotes[segment];
if ( text.length ) {
var toks = parse_emoji(text.join(' ') + ' ');
for(var q=0; q < toks.length; q++)
new_tokens.push(toks[q]);
text = [];
}
new_tokens.push(['
']);
if ( mine && l_room )
f.add_usage(l_room, emote);
text.push('');
} else
text.push(segment);
}
if ( text.length > 1 || (text.length === 1 && text[0] !== '') ) {
var toks = parse_emoji(text.join(' ') + ' ');
for(var q=0; q < toks.length; q++)
new_tokens.push(toks[q]);
}
}
return new_tokens;
}
this.update_ui_link();
}