var FFZ = window.FrankerFaceZ, utils = require("./utils"), constants = require("./constants"), helpers, conv_helpers, emote_helpers, bits_helpers, bits_service, bits_tags, HOP = Object.prototype.hasOwnProperty, FAV_MARKER = '', EXPLANATION_WARN = '
@
) open the user\'s moderation card when clicked.',
on_update: utils.toggle_cls('ffz-clickable-mentions')
}
// ---------------------
// Setup
// ---------------------
FFZ.prototype.setup_tokenization = function() {
// Tooltip Data
this._twitch_emotes = {};
this._twitch_emote_to_set = {};
this._twitch_set_to_channel = {};
this._link_data = {};
this.load_twitch_emote_data();
utils.toggle_cls('ffz-clickable-mentions')(this.settings.clickable_mentions);
try {
helpers = window.require && window.require("web-client/helpers/chat/chat-line-helpers");
} catch(err) { }
if ( ! helpers )
return this.log("Unable to get chat helper functions.");
try {
bits_helpers = window.require && window.require("web-client/utilities/bits/tokenize");
} catch(err) {
this.error("Unable to get bits tokenizer.", err);
}
bits_service = utils.ember_lookup('service:bits-emotes');
if ( ! bits_service )
bits_service = utils.ember_lookup('service:bits-rendering-config');
bits_tags = utils.ember_lookup('service:bits-tags');
try {
conv_helpers = window.require && window.require("web-client/helpers/twitch-conversations/conversation-line-helpers");
} catch(err) {
this.error("Unable to get conversation helper functions.", err);
}
try {
emote_helpers = window.require && window.require("web-client/utilities/tmi-emotes").default;
} catch(err) {
this.error("Unable to get tmi-emotes helper function.", err);
}
this.log("Hooking Ember chat line helpers.");
var f = this;
// Timestamp Display
helpers.getTime = function(e, show_ampm) {
if ( e === undefined || e === null )
return '?:??' + (f.settings.timestamp_seconds ? ':??' : '');
var hours = e.getHours(),
minutes = e.getMinutes(),
seconds = e.getSeconds(),
s = f.settings.twenty_four_timestamps,
pm = false;
if ( s < 2 ) {
if ( hours > 12 ) {
hours -= 12;
pm = true;
}
else if ( hours === 0 )
hours = 12;
}
return ((s === 1 || s === 3) && hours < 10 ? '0' : '') + hours + ':' + (minutes < 10 ? '0' : '') + minutes + (f.settings.timestamp_seconds ? ':' + (seconds < 10 ? '0' : '') + seconds : '') + (show_ampm && s < 2 ? '' + (pm ? 'pm' : 'am') + '' : '');
};
// Linkify Messages
helpers.linkifyMessage = function(tokens, delete_links) {
var show_deleted = f.settings.show_deleted_links;
return _.chain(tokens).map(function(token) {
if ( token.type === "text" )
token = token.text;
if ( ! _.isString(token) )
return token;
var matches = token.match(LINK);
if ( ! matches || ! matches.length )
return [token];
return _.zip(
token.split(LINK),
_.map(matches, function(e) {
var long = e.length > 255,
out = {
type: "link",
length: e.length,
isDeleted: ! show_deleted && (delete_links || long),
isLong: long,
isMailTo: e.indexOf("@") > -1 && (-1 === e.indexOf("/") || e.indexOf("@") < e.indexOf("/")),
text: e,
link: e
};
if ( ! out.isMailTo && ! e.match(/^(?:https?:\/\/)/) )
out.link = "http://" + e;
return out;
})
);
}).flatten().compact().value();
};
}
// ------------------------
// Display Name Formatting
// ------------------------
FFZ.prototype.format_display_name = function(display_name, user_id, disable_alias, disable_intl, disable_html) {
var setting = this.settings.username_display,
alias = this.aliases[user_id],
name_matches = ! display_name || display_name.trim().toLowerCase() === user_id,
tooltip,
display_name;
if ( setting === 0 )
display_name = user_id;
else if ( setting === 1 )
display_name = name_matches ? (display_name || (user_id && user_id.capitalize())) : user_id;
else {
display_name = utils.sanitize(display_name || (user_id && user_id.capitalize()));
if ( ! disable_intl && setting === 3 && ! name_matches )
display_name += disable_html ? ' (' + user_id + ')' : ' (' + user_id + ')';
else if ( ((disable_intl && setting === 3) || setting === 4) && ! name_matches )
tooltip = user_id;
}
if ( ! disable_alias && alias ) {
if ( display_name )
tooltip = display_name + (tooltip ? ' (' + tooltip + ')' : '');
display_name = utils.sanitize(alias);
}
return [display_name, tooltip];
}
// ---------------------
// Twitch Emote Data
// ---------------------
FFZ.prototype.load_twitch_emote_data = function(tries) {
var f = this;
f._twitch_set_to_channel[0] = "--global--";
f._twitch_set_to_channel[33] = "--turbo-faces--";
f._twitch_set_to_channel[42] = "--turbo-faces--";
f._twitch_set_to_channel[19194] = "--prime--";
f._twitch_set_to_channel[19151] = "--curse--";
this.log("Loading Twitch Emote Data (Try " + (tries || 0) + ")");
jQuery.ajax(constants.SERVER + "twitch_emotes.json")
.done(function(data) {
f.log("Loaded Twitch Emote Data", data);
for(var set_id in data) {
var set = data[set_id],
old_id = f._twitch_set_to_channel[set_id];
if ( ! set )
continue;
if ( ! old_id || old_id.indexOf('--') === -1 )
f._twitch_set_to_channel[set_id] = set.name;
for(var i=0, l = set.emotes.length; i < l; i++)
f._twitch_emote_to_set[set.emotes[i]] = set_id;
}
}).fail(function(data) {
f.log("Error loading Twitch Emote Data", data);
if ( data.status === 404 )
return;
tries = (tries || 0) + 1;
if ( tries < 10 )
setTimeout(f.load_twitch_emote_data.bind(f, tries), 1000);
});
}
// ---------------------
// Tooltip Rendering
// ---------------------
FFZ.prototype.render_tooltip = function(el) {
var f = this,
func = function() {
if ( this.classList.contains('ffz-bit') ) {
var amount = parseInt(this.getAttribute('data-amount').replace(/,/g, '')),
individuals = JSON.parse(this.getAttribute('data-individuals') || "null"),
prefix = this.getAttribute('data-prefix'),
tier = bits_service.ffz_get_tier(prefix, amount),
preview_url,
image,
out = utils.number_commas(amount) + ' Bit' + utils.pluralize(amount);
if ( f.settings.emote_image_hover )
preview_url = bits_service.ffz_get_preview(prefix, amount); //tier[1]);
if ( individuals && individuals.length > 1 ) {
out += '"});
}
}
if ( this.settings.parse_emoticons && this.settings.parse_emoticons !== 2 )
tokens = this.tokenize_emotes(user_id, room_id, tokens)
return tokens;
}
FFZ.prototype.render_token = function(render_links, warn_links, render_bits, token) {
if ( ! token )
return "";
if ( token.hidden )
return "";
else if ( token.type === "raw" )
return token.html;
else if ( token.type === "user" )
return '' + utils.sanitize(token.text) + '';
else if ( token.type === "emoticon" ) {
var src = token.imgSrc, srcset, cls, extra;
if ( token.ffzEmote ) {
var emote_set = this.emote_sets && this.emote_sets[token.ffzEmoteSet],
emote = emote_set && emote_set.emoticons && emote_set.emoticons[token.ffzEmote];
srcset = emote ? emote.srcSet : token.srcSet;
//extra = (emote ? ` data-ffz-emote="${emote.id}"` : '') + (emote_set ? ` data-ffz-set="${emote_set.id}"` : '');
extra = (emote ? ' data-ffz-emote="' + emote.id + '"' : '') + (emote_set ? ' data-ffz-set="' + emote_set.id + '"' : '')
} else if ( token.ffzEmoji ) {
var setting = this.settings.parse_emoji;
if ( setting === 0 || (setting === 1 && ! token.tw) || (setting === 2 && ! token.noto) || (setting === 3 && ! token.one) )
return token.altText;
src = setting === 3 ? token.one_src : (setting === 2 ? token.noto_src : token.tw_src);
//extra = ` data-ffz-emoji="${token.ffzEmoji}" height="18px"`;
extra = ' data-ffz-emoji="' + token.ffzEmoji + '" height="18px"';
cls = ' emoji';
} else {
var id = FFZ.src_to_id(src),
replacement = this.settings.replace_bad_emotes && constants.EMOTE_REPLACEMENTS[id];
//extra = ` data-emote="${id}" onerror="FrankerFaceZ._emote_mirror_swap(this)"`;
extra = ' data-emote="' + id + '" onerror="FrankerFaceZ._emote_mirror_swap(this)"';
if ( replacement ) {
src = constants.EMOTE_REPLACEMENT_BASE + replacement;
srcset = '';
} else
srcset = utils.build_srcset(id);
}
//return ``;
var f = this, prefix = '', suffix = '';
if ( token.modifiers && token.modifiers.length ) {
prefix = '';
suffix = _.map(token.modifiers, function(t) {
return '' + f.render_token(render_links, warn_links, render_bits, t) + '';
}).join('') + '';
extra += ' data-ffz-modifiers="' + utils.quote_attr(_.map(token.modifiers, function(t) { return t.ffzEmote }).join(' ')) + '" data-modifier-info="' + utils.quote_attr(JSON.stringify(_.map(token.modifiers, function(t) { return [t.ffzEmoteSet, t.ffzEmote] }))) + '"';
}
return prefix + '
' + suffix;
}
else if ( token.type === "tag" ) {
var link = Twitch.uri.game("Creative") + "/" + token.tag;
return '' + utils.sanitize(token.text) + '';
}
else if ( token.type === "link" ) {
var text = token.title || (token.isLong && '
Game: ' + utils.sanitize(data.game)
});
});
} else if ( video_info ) {
utils.api.get("videos/" + video_info[1], undefined, {version: 5})
.then(function(data) {
success(true, {
image: data.preview.large,
image_iframe: false,
html: '' + utils.sanitize(data.title) + ' [' + utils.time_to_string(data.length) + ']' +
'Channel: ' + utils.sanitize(data.channel.display_name) +
'
Game: ' + utils.sanitize(data.game)
})});
} else
this.ws_send("get_link", href, success);
}
}
}
// Deleted Links
var actual_href = href;
if ( token.isDeleted ) {
cls = 'deleted-link ' + cls;
href = '#';
} else if ( warn_links ) {
cls = 'warn-link deleted-link ' + cls;
href = '#';
}
//return `${utils.sanitize(text)}`;
return '' + utils.sanitize(text) + '';
}
else if ( token.type === "bits" ) {
var tier = render_bits && bits_service.ffz_get_tier(token.prefix, token.amount) || [null, null];
if ( ! tier[1] )
return 'cheer' + token.amount;
var prefix = utils.quote_attr(token.prefix);
return '';
}
else if ( token.type === 'bits-tag' ) {
return '' + utils.sanitize(token.tag) + '';
}
else if ( token.type === "deleted" )
return '×××';
//return `×××`;
else if ( token.type === "mention" )
return '' + utils.sanitize(token.user) + '';
//return `${utils.sanitize(token.user)}`;
else if ( token.deletedLink || token.text )
return utils.sanitize(token.text);
else if ( typeof token !== "string" )
return '[unknown token]';
//return `[unknown token]`;
return utils.sanitize(token);
}
FFZ.prototype.render_tokens = function(tokens, render_links, warn_links, render_bits) {
return _.map(tokens, this.render_token.bind(this, render_links, warn_links, render_bits)).join("");
}
// ---------------------
// Creative Tags
// ---------------------
FFZ.prototype.tokenize_ctags = function(tokens, tags_only) {
"use strict";
if ( typeof tokens === "string" )
tokens = [tokens];
var banned_tags = window.SiteOptions && SiteOptions.creative_banned_tags && SiteOptions.creative_banned_tags.split(',') || [],
new_tokens = [];
for(var i=0, l = tokens.length; i < l; i++) {
var token = tokens[i];
if ( ! token )
continue;
if ( typeof token !== "string" )
if ( token.type === "text" )
token = token.text;
else {
! tags_only && new_tokens.push(token);
continue;
}
var segments = token.split(' '),
text = [], segment, tag;
for(var x=0,y=segments.length; x < y; x++) {
segment = segments[x];
tag = segment.substr(1).toLowerCase();
if ( segment.charAt(0) === '#' && banned_tags.indexOf(tag) === -1 ) {
if ( text.length ) {
! tags_only && new_tokens.push({type: "text", text: text.join(' ') + ' '});
text = [];
}
new_tokens.push({type: "tag", text: segment, tag: tag});
text.push('');
} else
text.push(segment);
}
if ( ! tags_only && (text.length > 1 || (text.length === 1 && text[0] !== '')) )
new_tokens.push({type: "text", text: text.join(' ')});
}
return new_tokens;
}
// ---------------------
// Emoticon Processing
// ---------------------
FFZ.prototype.tokenize_users = function(tokens) {
"use strict";
if ( typeof tokens === "string" )
tokens = [tokens];
var new_tokens = [];
for(var i=0, l=tokens.length; i < l; i++) {
var token = tokens[i];
if ( ! token )
continue;
if ( typeof token !== "string" )
if ( token.type === "text" )
token = token.text;
else {
new_tokens.push(token);
continue;
}
var segments = token.split(/(@[a-z0-9][a-z0-9_]{3,24})/i);
for(var x=0, y = segments.length; x < y; x += 2) {
var text = segments[x] || '',
match = segments[x+1] || '';
if ( text.length )
new_tokens.push({type: 'text', text: text});
if ( match.length )
new_tokens.push({type: 'user', text: match, user: match.substr(1)});
}
}
return new_tokens;
}
FFZ.prototype.tokenize_emotes = function(user, room, tokens, do_report) {
"use strict";
var sets = this.getEmotes(user, room),
emotes = {},
emote,
new_tokens = [];
if ( ! tokens || ! tokens.length || ! sets || ! sets.length )
return tokens;
// Build an object with all of our emotes.
for(var i=0; i < sets.length; i++) {
var emote_set = this.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;
}
}
if ( typeof tokens === "string" )
tokens = [tokens];
var last_token;
for(var i=0, l=tokens.length; i < l; i++) {
var token = tokens[i];
if ( ! token )
continue;
if ( typeof token !== "string" )
if ( token.type === "text" )
token = token.text;
else {
if ( ! token.modifiers && token.type === 'emoticon' )
token.modifiers = [];
new_tokens.push(token);
last_token = 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];
// Is this emote a modifier?
if ( emote.modifier && last_token && last_token.modifiers && (!text.length || (text.length === 1 && text[0] === '')) ) {
if ( last_token.modifiers.indexOf(emote.token) === -1 )
last_token.modifiers.push(emote.token);
if ( do_report && room )
this.add_usage(room, emote);
continue;
}
if ( text.length ) {
// We have pending text. Join it together, with an extra space
// on the end for good measure.
var token = {type: "text", text: text.join(' ') + ' '};
new_tokens.push(token);
if ( token.text.trim().length )
last_token = token;
text = []
}
// Push this emote to the tokens.
var token = _.extend({}, emote.token);
token.modifiers = [];
new_tokens.push(token);
last_token = token;
if ( do_report && room )
this.add_usage(room, emote);
// Finally, push an empty string to text so that this emote gets spaced.
text.push('');
} else
text.push(segment);
}
// Add any left over text from this segment.
if ( text.length > 1 || (text.length === 1 && text[0] !== '') )
new_tokens.push({type: "text", text: text.join(' ')});
}
return new_tokens;
}
// ---------------------
// Emoji Processing
// ---------------------
FFZ.prototype.tokenize_emoji = function(tokens) {
"use strict";
if ( ! tokens || ! tokens.length || ! this.emoji_data )
return tokens;
if ( typeof tokens === "string" )
tokens = [tokens];
var new_tokens = [];
for(var i=0, l=tokens.length; i < l; i++) {
var token = tokens[i];
if ( ! token )
continue;
if ( typeof token !== "string" )
if ( token.type === "text" )
token = token.text;
else {
new_tokens.push(token);
continue;
}
var 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 = this.emoji_data[eid];
if ( data ) {
if ( text && text.length )
new_tokens.push(text);
new_tokens.push(_.extend({modifiers: []}, data.token));
text = null;
} else
text = (text || '') + match;
}
}
if ( text && text.length )
new_tokens.push(text);
}
return new_tokens;
}
// ---------------------
// Mention Parsing
// ---------------------
FFZ._regex_cache = {};
FFZ._get_regex = function(word) {
return FFZ._regex_cache[word] = FFZ._regex_cache[word] || RegExp("\\b" + reg_escape(word) + "\\b", "ig");
}
FFZ._words_to_regex = function(list) {
var regex = FFZ._regex_cache[list];
if ( ! regex ) {
var reg = "";
for(var i=0; i < list.length; i++) {
if ( ! list[i] )
continue;
reg += (reg ? "|" : "") + (list[i].substr(0,6) === "regex:" ? list[i].substr(6) : reg_escape(list[i]));
}
regex = FFZ._regex_cache[list] = new RegExp("(^|.*?" + constants.SEPARATORS + ")(" + reg + ")(?=$|" + constants.SEPARATORS + ")", "ig");
}
return regex;
}
FFZ.prototype.tokenize_mentions = function(tokens) {
var mention_words = this.settings.keywords;
if ( ! mention_words || ! mention_words.length )
return tokens;
if ( typeof tokens === "string" )
tokens = [tokens];
var regex = FFZ._words_to_regex(mention_words),
new_tokens = [];
for(var i=0; i < tokens.length; i++) {
var token = tokens[i];
if ( token.type === "text" )
token = token.text;
if ( ! _.isString(token) || ! token.match(regex) ) {
new_tokens.push(token);
continue;
}
token = token.replace(regex, function(all, prefix, match) {
new_tokens.push(prefix);
new_tokens.push({
type: "mention",
length: match.length,
user: match,
isOwnMessage: false,
});
return "";
});
if ( token )
new_tokens.push(token);
}
return new_tokens;
}
// ---------------------
// Handling Bad Stuff
// ---------------------
FFZ.prototype._deleted_link_click = function(e) {
if ( ! this.classList.contains("deleted-link") )
return true;
// Stop from Navigating
e.preventDefault();
// Get the URL
var link = this.getAttribute('data-url'),
text = this.getAttribute('data-text') || link,
f = FrankerFaceZ.get();
// Delete Old Stuff
this.classList.remove('deleted-link');
this.classList.remove('warn-link');
// Set up the Link
this.href = link;
this.target = "_blank";
this.textContent = text;
// Refresh tipsy.
jQuery(this).trigger('mouseout').trigger('mouseover');
}
// ---------------------
// History Loading
// ---------------------
/*FFZ.prototype.parse_history = function(history, purged, bad_ids, room_id, delete_links, tmiSession, per_line) {
var i = history.length, was_cleared = false;
purged = purged || {};
bad_ids = bad_ids || {};
while(i--) {
var msg = history[i],
msg_id = msg.tags && msg.tags.id,
is_deleted = msg.ffz_deleted = purged[msg.from] || (msg_id && bad_ids[msg_id]) || false;
if ( is_deleted && ! this.settings.prevent_clear )
msg.deleted = true;
if ( ! msg.room && room_id )
msg.room = room_id;
if ( typeof msg.date === "string" || typeof msg.date === "number" )
msg.date = utils.parse_date(msg.date);
if ( ! msg.color )
msg.color = msg.tags && msg.tags.color ? msg.tags.color : tmiSession && msg.from ? tmiSession.getColor(msg.from) : "#755000";
if ( ! msg.labels || ! msg.labels.length ) {
var labels = msg.labels = [];
if ( msg.room && msg.room === msg.from )
labels.push("owner");
else if ( msg.tags ) {
var ut = msg.tags['user-type'];
if ( ut === 'mod' || ut === 'staff' || ut === 'admin' || ut === 'global_mod' )
labels.push(ut);
}
if ( msg.tags ) {
if ( msg.tags.turbo )
labels.push("turbo");
if ( msg.tags.subscriber )
labels.push("subscriber");
}
}
if ( ! msg.style ) {
if ( msg.from === "jtv" )
msg.style = "admin";
else if ( msg.from === "twitchnotify" )
msg.style = "notification";
}
if ( msg.tags && typeof msg.tags.emotes === "string" )
msg.tags.emotes = utils.uncompressEmotes(msg.tags.emotes);
if ( msg.tags && typeof msg.tags.badges === "string" )
msg.tags.badges = utils.uncompressBadges(msg.tags.badges);
if ( ! msg.cachedTokens || ! msg.cachedTokens.length )
this.tokenize_chat_line(msg, true, delete_links);
// CLEARCHAT
if ( msg.tags && msg.tags.target === '@@' )
was_cleared = true;
else if ( msg.tags && msg.tags.target ) {
var ban_reason = msg.tags && msg.tags['ban-reason'],
ban_id = ban_reason && constants.UUID_TEST.exec(ban_reason);
if ( ban_id ) {
bad_ids[ban_id[1]] = true;
ban_reason = ban_reason.substr(0, ban_reason.length - ban_id[0].length);
msg.tags['ban-reason'] = ban_reason ? ban_reason : undefined;
} else
purged[msg.tags.target] = true;
}
// Per-line
if ( per_line && ! per_line(msg) )
break;
}
return [history, purged, was_cleared];
}*/