diff --git a/.gitignore b/.gitignore index 2d22fb2f..fb3e9c14 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ node_modules npm-debug.log build +dist Extension Building Old Files badges diff --git a/README.md b/README.md index 4b8f5dd0..cbf1a4ce 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ FrankerFaceZ ============ -Copyright (c) 2016 Dan Salvato LLC +Copyright (c) 2017 Dan Salvato LLC Licensed under the Apache License, Version 2.0. See LICENSE. @@ -12,26 +12,18 @@ Developing FrankerFaceZ uses node.js to manage development dependencies and to run an HTTP server for development. To get everything you need: -1. Install node.js -2. Run ```npm install -g gulp``` to install the ```gulp``` command line utility. -3. Run ```npm install``` within the FrankerFaceZ directory. +1. Install node.js and npm +2. Run ```npm install`` within the FrankerFaceZ directory. -From there, you can use gulp to build the extension from source simply by -running ```gulp```. For development, you can instruct gulp to watch the source -files for changes and re-build automatically with ```gulp watch``` +From there, you can use npm to build the extension from source simply by +running ```npm run build```. For development, you can instruct gulp to watch +the source files for changes and re-build automatically with ```npm start``` FrankerFaceZ comes with a local development server that listens on port 8000 and it serves up local development copies of files, falling back to the CDN -when a local copy of a file isn't present. To start the server, -run ```gulp server``` +when a local copy of a file isn't present. -For convenience, the server is run automatically along with ```gulp watch``` - - -Use the command ```/ffz developer_mode on``` or ```/ffz developer_mode off``` -in Twitch chat to toggle developer mode on or off. You must then refresh the -page for changes to take effect. If FFZ is not working or the command otherwise -fails to work, you can open the JavaScript console on twitch.tv and run -```localStorage.ffzDebugMode = true;``` or -```localStorage.ffzDebugMode = false;``` to enable or disable the feature. \ No newline at end of file +To make FrankerFaceZ load from your local development server, you must set +the local storage variable ```ffzDebugMode``` to true. Just run the following +in your console on Twitch: ```localStorage.ffzDebugMode = true;``` diff --git a/changelog.html b/changelog.html index b38085b3..96dad766 100644 --- a/changelog.html +++ b/changelog.html @@ -1,70 +1,7 @@ -
/ffz promote <id>
with the appropriate message id. The easiest
- way to do this is to add a custom in-line moderation icon with the command
- /ffz promote {id}
/ffz promote <id>
with the appropriate message id. The easiest
+ way to do this is to add a custom in-line moderation icon with the command
+ /ffz promote {id}
' + badge[0] + '
');
- }
-
- utils.prompt(
- "Hidden Badges",
- "Please enter a comma-separated list of badges that you would like to be hidden in chat. You can use the special value game
to hide all the game-specific badges at once.Possible Values: " + output.join(", "), - old_val, - function(new_val) { - if ( new_val === null || new_val === undefined ) - return; - - f.settings.set("hidden_badges", _.unique(new_val.trim().toLowerCase().split(/\s*,\s*/)).without("")); - }, 600 - ) - } -}; - - -FFZ.settings_info.sub_notice_badges = { - type: "boolean", - value: false, - - category: "Chat Appearance", - name: "Old-Style Subscriber Notice Badges", - no_bttv: true, - - help: "Display a subscriber badge on old-style chat messages about new subscribers.", - - on_update: function(val) { - this.toggle_style('badges-sub-notice', ! this.has_bttv && ! val); - this.toggle_style('badges-sub-notice-on', ! this.has_bttv && val); - } -}; - - -FFZ.settings_info.legacy_badges = { - type: "select", - options: { - 0: "Default", - 1: "Moderator Only", - 2: "Mod + Turbo", - 3: "All Legacy Badges" - }, - value: 0, - - category: "Chat Appearance", - - name: "Legacy Badges", - help: "Use the old, pre-vector chat badges from Twitch in place of the new.", - - process_value: utils.process_int(0, 0, 3), - - on_update: function(val) { - this.toggle_style('badges-legacy', val === 3); - this.toggle_style('badges-legacy-mod', val !== 0); - this.toggle_style('badges-legacy-turbo', val > 1); - } -}; - - -FFZ.settings_info.transparent_badges = { - type: "select", - options: { - 0: "Default", - 1: "Rounded", - 2: "Circular", - 3: "Circular (Color Only)", - 4: "Circular (Color Only, Small)", - 5: "Transparent", - 6: "Transparent (Colored)" - }, - - value: 0, - - category: "Chat Appearance", - no_bttv: 6, - - name: "Badge Style", - help: "Make badges appear rounded, completely circular, or transparent with no background at all.", - - process_value: utils.process_int(0, 0, 5), - - on_update: function(val) { - if ( this.has_bttv_6 ) - return; - - this.toggle_style('badges-rounded', val === 1); - this.toggle_style('badges-circular', val === 2 || val === 3 || val === 4); - this.toggle_style('badges-blank', val === 3 || val === 4); - this.toggle_style('badges-circular-small', val === 4); - this.toggle_style('badges-transparent', val >= 5); - utils.toggle_cls('ffz-transparent-badges')(val >= 5); - utils.toggle_cls('ffz-blank-badges')(val === 3 || val === 4); - - // Update existing chat lines. - var CL = utils.ember_resolve('component:chat/chat-line'), - CW = utils.ember_resolve('component:twitch-conversations/conversation-window'), - DP = utils.ember_resolve('component:chat/from-display-preview'), - views = (CL || CW || DP) ? utils.ember_views() : []; - - for(var vid in views) { - var view = views[vid]; - if ( CL && view instanceof CL && view.buildBadgesHTML ) - view.$('.badges').replaceWith(view.buildBadgesHTML()); - else if ( DP && view instanceof DP && view.ffzRenderBadges ) - view.ffzRenderBadges(); - else if ( CW && view instanceof CW && view.ffzReplaceBadges ) - view.ffzReplaceBadges(); - } - } -}; - - -// -------------------- -// Initialization -// -------------------- - -FFZ.prototype.setup_badges = function() { - this.log("Preparing badge system."); - if ( ! this.has_bttv_6 ) { - var val = this.settings.transparent_badges; - this.toggle_style('badges-rounded', val === 1); - this.toggle_style('badges-circular', val === 2 || val === 3 || val === 4); - this.toggle_style('badges-blank', val === 3 || val === 4); - this.toggle_style('badges-circular-small', val === 4); - this.toggle_style('badges-transparent', val >= 5); - - utils.toggle_cls('ffz-transparent-badges')(val >= 5); - utils.toggle_cls('ffz-blank-badges')(val === 3 || val === 4); - utils.toggle_cls('ffz-no-loyalty')(!this.settings.loyalty_badges); - } - - if ( ! this.has_bttv ) { - this.toggle_style('badges-sub-notice', ! this.settings.sub_notice_badges); - this.toggle_style('badges-sub-notice-on', this.settings.sub_notice_badges); - } - - this.toggle_style('badges-legacy', this.settings.legacy_badges === 3); - this.toggle_style('badges-legacy-mod', this.settings.legacy_badges !== 0); - this.toggle_style('badges-legacy-turbo', this.settings.legacy_badges > 1); - - this.log("Creating badge style element."); - var s = this._badge_style = document.createElement('style'); - s.id = "ffz-badge-css"; - document.head.appendChild(s); - - this.log("Generating CSS for existing API badges."); - for(var badge_id in this.badges) - if ( this.badges.hasOwnProperty(badge_id) ) - utils.update_css(s, badge_id, utils.badge_css(this.badges[badge_id])); - - this.log("Generating CSS for existing Twitch badges."); - for(var badge_id in CSS_BADGES) { - var badge_data = CSS_BADGES[badge_id], - klass = BADGE_KLASSES[badge_id] || badge_id; - for(var version in badge_data) - utils.update_css(s, 'twitch-' + badge_id + '-' + version, utils.cdn_badge_css(klass, version, badge_data[version])); - } - - this.log("Loading badges."); - this.load_badges(); -} - - -// -------------------- -// Reloading Badges -// -------------------- - -FFZ.ws_commands.reload_badges = function() { - this.load_badges(); -} - - -FFZ.ws_commands.set_badge = function(data) { - var user_id = data[0], - slot = data[1], - badge = data[2], - - user = this.users[user_id] = this.users[user_id] || {}, - badges = user.badges = user.badges || {}; - - if ( typeof badge === "number" ) - badge = {id: badge}; - - if ( badge === undefined || badge === null ) - badges[slot] = null; - else - badges[slot] = badge; -} - - -// -------------------- -// Badge Selection -// -------------------- - -FFZ.prototype.get_badges = function(user, room_id, badges, msg) { - var data = this.users[user], - room = this.rooms[room_id], - room_data = room && room.users && room.users[user], - hidden_badges = this.settings.hidden_badges, - badge_data = data && data.badges || {}; - - if ( room_data && room_data.badges ) - badge_data = _.extend({}, badge_data, room_data.badges); - - if ( ! badge_data || ! this.settings.show_badges ) - return badges; - - for(var slot in badge_data) { - var badge = badge_data[slot]; - if ( ! badge_data.hasOwnProperty(slot) || ! badge ) - continue; - - var badge_id = badge.real_id || badge.id, - full_badge = this.badges[badge_id] || {}, - full_badge_id = full_badge.real_id || full_badge.id, - old_badge = badges[slot], - - hide_key = (full_badge.source_ext ? this._apis[full_badge.source_ext].name_key : 'ffz') + '-' + (full_badge.name || full_badge.id); - - if ( hidden_badges.indexOf(hide_key) !== -1 ) - continue; - - if ( full_badge.visible !== undefined ) { - var visible = full_badge.visible; - if ( typeof visible === "function" ) - visible = visible.call(this, room_id, user, msg, badges); - - if ( ! visible ) - continue; - } - - if ( old_badge ) { - var replaces = badge.hasOwnProperty('replaces') ? badge.replaces : full_badge.replaces, - replace_mode = badge.replace_mode || full_badge.replace_mode || 'merge'; - if ( ! replaces ) - continue; - - if ( replace_mode === 'merge' ) { - old_badge.image = badge.image || null; - old_badge.klass += ' ffz-badge-replacement ffz-replacer-ffz-badge-' + (badge_id || full_badge_id); - old_badge.title += ', ' + (badge.title || full_badge.title); - continue; - - } else if ( replace_mode === 'keep_title' ) { - var b = badges[slot] = this._get_badge_object(badge, full_badge); - b.title = old_badge.title + ', ' + b.title; - continue; - - } else if ( replace_mode === 'title_only' ) { - old_badge.title += ', ' + (badge.title || full_badge.title); - continue; - } - } - - badges[slot] = this._get_badge_object(badge, full_badge); - } - - return badges; -} - -FFZ.prototype._get_badge_object = function(badge, full_badge) { - var id = badge.real_id || badge.id || full_badge.real_id || full_badge.id; - return { - id: id, - klass: 'ffz-badge-' + id, - title: badge.title || full_badge.title || ('Unknown FFZ Badge\nID: ' + id), - image: badge.image, - full_image: full_badge.image, - color: badge.color, - no_tooltip: badge.no_tooltip || full_badge.no_tooltip, - click_action: badge.click_action || full_badge.click_action, - click_url: badge.click_url || full_badge.click_url, - no_invert: badge.no_invert || full_badge.no_invert, - no_color: badge.no_color || full_badge.no_color, - invert_invert: badge.invert_invert || full_badge.invert_invert, - transparent: badge.transparent || full_badge.transparent || (badge.color || full_badge.color) === "transparent", - extra_css: (badge.extra_css || full_badge.extra_css) - } -} - - -FFZ.prototype.get_line_badges = function(msg) { - var room = msg.get && msg.get('room') || msg.room, - from = msg.get && msg.get('from') || msg.from, - tags = msg.get && msg.get('tags') || msg.tags || {}, - badge_tag = tags.badges || {}; - - // Twitch Badges - var badges = this.get_twitch_badges(badge_tag, room); - - // FFZ Badges - return this.get_badges(from, room, badges, msg); -} - - -FFZ.prototype.get_twitch_badges = function(badge_tag, room_id) { - var badges = {}, - hidden_badges = this.settings.hidden_badges, - - last_id = -1, - had_last = false, - - service = utils.ember_lookup('service:badges'), - badgeCollection = service && service.badgeCollection, - - globals = badgeCollection && badgeCollection.global || {}, - channel = badgeCollection && badgeCollection.channel || {}; - - // Is this the right channel? - if ( room_id && room_id !== channel.broadcasterName ) { - var ffz_room = this.rooms && this.rooms[room_id]; - channel = ffz_room && ffz_room.badges || {}; - } - - // Whisper Chat Lines have a non-associative array for some reason. - if ( Array.isArray(badge_tag) ) { - var val = badge_tag; - badge_tag = {}; - for(var i=0; i < val.length; i++) - badge_tag[val[i].id] = val[i].version; - } - - // VoD Chat lines don't have the badges pre-parsed for some reason. - else if ( typeof badge_tag === 'string' ) - badge_tag = utils.parse_badge_tag(badge_tag); - - - for(var badge in badge_tag) { - var version = badge_tag[badge]; - if ( ! badge_tag.hasOwnProperty(badge) || version === undefined || version === null ) - continue; - - var versions = channel[badge] || globals[badge], - binfo = versions && versions.versions && versions.versions[version], - is_game = badge.substr(-2) === '_1'; - - if ( hidden_badges.indexOf(badge) !== -1 || (is_game && hidden_badges.indexOf('game') !== -1) ) - continue; - - if ( BADGE_POSITIONS.hasOwnProperty(badge) ) - last_id = BADGE_POSITIONS[badge]; - else { - last_id = had_last ? last_id + 1 : 15; - had_last = true; - } - - var is_known = BADGE_POSITIONS.hasOwnProperty(badge) || OTHER_KNOWN.indexOf(badge) !== -1; - - badges[last_id] = { - klass: (BADGE_KLASSES[badge] || badge) + (is_known ? '' : ' unknown-badge') + ' version-' + version, - title: binfo && binfo.title || BADGE_NAMES[badge] || badge.capitalize(), - click_url: binfo && binfo.click_action === 'visit_url' && binfo.click_url, - no_invert: ! (versions && versions.allow_invert) && NO_INVERT_BADGES.indexOf(badge) !== -1, - no_color: ! CSS_BADGES.hasOwnProperty(badge), - invert_invert: (versions && versions.invert_invert) || INVERT_INVERT_BADGES.indexOf(badge) !== -1, - transparent: TRANSPARENT_BADGES.indexOf(badge) !== -1 - }; - - if ( ! is_known && binfo ) { - badges[last_id].image = binfo.image_url_1x; - if ( binfo.image_url_2x || binfo.image_url_4x ) - badges[last_id].srcSet = 'url("' + binfo.image_url_1x + '") 1x' + (binfo.image_url_2x ? ', url("' + binfo.image_url_2x + '") 2x' : '') + (binfo.image_url_4x ? ', url("' + binfo.image_url_4x + '") 4x' : ''); - } - } - - return badges; -} - - -// -------------------- -// Render Badge -// -------------------- - -FFZ.prototype.render_badges = function(badges) { - var out = [], - setting = this.settings.transparent_badges; - - for(var key in badges) { - var badge = badges[key], - klass = badge.klass, - css = '', - is_colored = !(badge.no_color !== undefined ? badge.no_color : badge.transparent); - - if ( badge.image ) - if ( is_colored && setting === 6 ) - css += WEBKIT + 'mask-image:url("' + utils.quote_attr(badge.image) + '");'; - else - css += 'background-image:url("' + utils.quote_attr(badge.image) + '");'; - - if ( badge.srcSet ) - if ( is_colored && setting === 6 ) - css += WEBKIT + 'mask-image:' + WEBKIT + 'image-set(' + badge.srcSet + ');'; - else - css += 'background-image:' + WEBKIT + 'image-set(' + badge.srcSet + ');'; - - if ( badge.color ) - if ( is_colored && setting === 6 ) - css += 'background: linear-gradient(' + badge.color + ',' + badge.color + ');'; - else - css += 'background-color:' + badge.color + ';' - - if ( badge.extra_css ) - css += badge.extra_css; - - if ( badge.click_url ) - klass += ' click_url'; - - if ( badge.click_action ) - klass += ' click_action'; - - if ( badge.no_invert ) - klass += ' no-invert'; - - if ( badge.invert_invert ) - klass += ' invert-invert'; - - if ( is_colored && setting === 6 ) - klass += ' colored'; - - if ( badge.transparent ) - klass += ' transparent'; - - out.push('
'); - } - - return out.join(""); -} - - -// -------------------- -// Extension Support -// -------------------- - -FFZ.prototype.bttv_badges = function(data) { - if ( ! this.settings.show_badges ) - return; - - var user_id = data.sender, - user = this.users[user_id], - room = this.rooms[data.room], - room_data = room && room.users && room.users[user_id], - badges_out = [], - insert_at = -1, - - hidden_badges = this.settings.hidden_badges, - alpha = BetterTTV.settings.get('alphaTags'); - - if ( ! data.badges ) - data.badges = []; - - // Determine where in the list to insert these badges. - // Also, strip out banned badges while we're at it. - for(var i=0; i < data.badges.length; i++) { - var badge = data.badges[i], - space_ind = badge.type.indexOf(' '), - hidden_key = space_ind !== -1 ? badge.type.substr(0, space_ind) : badge.type; - - if ( hidden_key.indexOf('twitch-') === 0 ) - hidden_key = hidden_key.substr(7); - - if ( BTTV_TYPE_REPLACEMENTS.hasOwnProperty(hidden_key) ) - hidden_key = BTTV_TYPE_REPLACEMENTS[hidden_key]; - else { - var ind = hidden_key.indexOf('-'); - if ( ind !== -1 ) - hidden_key = hidden_key.substr(0, ind); - } - - var is_game = hidden_key.substr(-2) === '_1'; - if ( hidden_badges.indexOf(hidden_key) !== -1 || (is_game && hidden_badges.indexOf('game') !== -1) ) { - data.badges.splice(i, 1); - i--; - continue; - } - - if ( insert_at === -1 && (badge.type === "subscriber" || badge.type === "turbo" || badge.type.substr(0, 7) === 'twitch-') ) - insert_at = i; - } - - var badge_data = user && user.badges || {}; - if ( room_data && room_data.badges ) - badge_data = _.extend({}, badge_data, room_data.badges); - - // If there's no user, we're done now. - if ( ! badge_data ) - return; - - // We have a user. Start replacing badges. - for (var slot in badge_data) { - var badge = badge_data[slot]; - if ( ! badge_data.hasOwnProperty(slot) || ! badge ) - continue; - - var badge_id = badge.real_id || badge.id, - full_badge = this.badges[badge_id] || {}, - full_badge_id = full_badge.real_id || full_badge.id, - desc = badge.title || full_badge.title, - style = "", - klass = 'ffz-badge-' + badge_id + (alpha ? ' alpha' : ''), - - hide_key = (full_badge.source_ext ? this._apis[full_badge.source_ext].name_key : 'ffz') + '-' + (full_badge.name || full_badge.id); - - if ( hidden_badges.indexOf(hide_key) !== -1 ) - continue; - - if ( full_badge.visible !== undefined ) { - var visible = full_badge.visible; - if ( typeof visible === "function" ) - visible = visible.call(this, data.room, user_id); - - if ( ! visible ) - continue; - } - - if ( alpha && badge.transparent_image ) - style += 'background-image: url("' + badge.transparent_image + '");'; - else 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="' + utils.quote_attr(style); - - var replaces = badge.hasOwnProperty('replaces') ? badge.replaces : full_badge.replaces, - replace_mode = badge.replace_mode || full_badge.replace_mode || 'merge'; - - if ( replaces ) { - var replaced = false; - for(var i=0; i < data.badges.length; i++) { - var b = data.badges[i]; - if ( b.type === full_badge.replaces_type ) { - if ( replace_mode === 'merge' ) { - b.type += " ffz-badge-replacement ffz-replacer-ffz-badge-" + (badge_id || full_badge_id); - b.description += ", " + (badge.title || full_badge.title) + - (badge.image ? '" style="background-image: url(' + utils.quote_attr('"' + badge.image + '"') + ')' : ''); - - } else if ( replace_mode === 'keep_title' ) { - data.badges[i] = { - type: klass, - name: '', - description: b.description + ', ' + desc - }; - - } else if ( replace_mode === 'title_only' ) { - b.description += ', ' + (badge.title || full_badge.title); - } else { - data.badges[i] = {type: klass, name: '', description: desc}; - } - - replaced = true; - break; - } - } - - if ( replaced ) - continue; - } - - badges_out.push([(insert_at == -1 ? 1 : -1) * slot, {type: klass, 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]); - } -} - - -// -------------------- -// Badge Loading -// -------------------- - -FFZ.bttv_known_bots = ["nightbot","moobot","sourbot","xanbot","manabot","mtgbot","ackbot","baconrobot","tardisbot","deejbot","valuebot","stahpbot"]; - -FFZ.prototype.load_badges = function(callback, tries) { - var f = this; - jQuery.getJSON(constants.API_SERVER + "v1/badges") - .done(function(data) { - var badge_total = 0, - badge_count = 0, - badge_data = {}; - - for(var i=0; i < data.badges.length; i++) { - var badge = data.badges[i]; - if ( badge && badge.name ) { - f._load_badge_json(badge.id, badge); - badge_total++; - } - } - - if ( data.users ) - for(var badge_id in data.users) - if ( data.users.hasOwnProperty(badge_id) && f.badges[badge_id] ) { - var badge = f.badges[badge_id], - users = data.users[badge_id]; - - badge_data[badge.name] = users.length; - - for(var i=0; i < users.length; i++) { - var user = users[i], - ud = f.users[user] = f.users[user] || {}, - badges = ud.badges = ud.badges || {}; - - badge_count++; - badges[badge.slot] = {id: badge.id}; - } - - f.log('Added "' + badge.name + '" badge to ' + utils.number_commas(users.length) + ' users.'); - } - - // Special Badges - var zw = f.users.zenwan = f.users.zenwan || {}, - badges = zw.badges = zw.badges || {}; - if ( ! badges[1] ) - badge_count++; - badges[1] = {id: 2, image: "//cdn.frankerfacez.com/script/momiglee_badge.png", title: "WAN"}; - - f.log("Loaded " + utils.number_commas(badge_count) + " total badges across " + badge_total + " types."); - typeof callback === "function" && callback(true, badge_count, badge_total, badge_data); - - }).fail(function(data) { - if ( data.status === 404 ) - return typeof callback === "function" && callback(false); - - tries = (tries || 0) + 1; - if ( tries < 10 ) - return setTimeout(f.load_badges.bind(f, callback, tries), 500 + 500*tries); - - f.error("Unable to load badge data. [HTTP Status " + data.status + "]", data); - typeof callback === "function" && callback(false); - }); -} - - -FFZ.prototype._load_badge_json = function(badge_id, data) { - this.badges[badge_id] = data; - if ( data.replaces ) { - data.replaces_type = data.replaces; - data.replaces = true; - } - - if ( data.name === 'bot' ) - data.visible = function(r,user) { return !(this.has_bttv && FFZ.bttv_known_bots.indexOf(user)!==-1); }; - - if ( data.name === 'developer' || data.name === 'supporter' ) - data.click_url = 'https://www.frankerfacez.com/donate'; - - utils.update_css(this._badge_style, badge_id, utils.badge_css(data)); -} \ No newline at end of file diff --git a/src/commands.js b/src/commands.js deleted file mode 100644 index d667b18a..00000000 --- a/src/commands.js +++ /dev/null @@ -1,531 +0,0 @@ -var FFZ = window.FrankerFaceZ, - constants = require('./constants'), - utils = require('./utils'), - - KNOWN_COMMANDS = ['ffz', 'unban', 'ban', 'timeout', 'r9kbeta', 'r9kbetaoff', 'slow', 'slowoff', 'subscribers', 'subscribersoff', 'mod', 'unmod', 'me', 'emoteonly', 'emoteonlyoff', 'host', 'unhost', 'commercial'], - - STATUS_CODES = { - 400: "Bad Request", - 401: "Unauthorized", - 403: "Forbidden", - 404: "Not Found", - 500: "Internal Server Error" - }, - - format_result = function(response) { - if ( typeof response === "string" ) - return response; - - else if ( Array.isArray(response) ) - return _.map(response, format_result).join(", "); - - return JSON.stringify(response); - }, - - ObjectPath = require('./ObjectPath'); - - -FFZ.ObjectPath = ObjectPath; - -// ----------------- -// Settings -// ----------------- - -FFZ.settings_info.command_aliases = { - type: "button", - value: [], - - category: "Chat Moderation", - no_bttv: 6, - - name: "Command Aliases", - help: "Define custom commands for chat that are shortcuts for other commands or messages to send in chat.", - - on_update: function() { - this.cache_command_aliases(); - }, - - method: function() { - var f = this, - old_val = [], - input = utils.createElement('textarea'); - - input.style.marginBottom = '20px'; - - for(var i=0; i < this.settings.command_aliases.length; i++) { - var pair = this.settings.command_aliases[i], - name = pair[0], - command = pair[1], - label = pair[2]; - - old_val.push(name + (label ? ' ' + label : '') + '=' + command); - } - - utils.prompt( - "Command Aliases", - "Please enter a list of custom commands that you would like to use in Twitch chat. " + - "One item per line. To send multiple commands, separate them with<LINE>
. " +
- "Variables, such as arguments you provide running the custom command, can be inserted into the output.boop <user>=/timeout {0} 15 Boop!
{0}
, {1}
, {2}
, etc. will be replaced with any arguments you've supplied. " +
- "Follow an argument index with a $
to also include all remaining arguments.boop=/timeout {0} 15 {1$}
{room} | chat room's name | " + - "{room_name} | chat room's name |
{room_display_name} | chat room's display name | " + - "{room_id} | chat room's numeric ID |
Store | Price Cut | Current | Regular |
---|
' + output + '
'; - } - }); -} - - -FFZ.prototype.modify_feed_comment = function(component) { - var f = this; - utils.ember_reopen_view(component, { - ffz_init: function(for_set) { - var el = this.get('element'), - message = this.get('comment.body'), - emotes = parse_emotes(this.get('comment.emotes')), - user_id = this.get('comment.user.login'), - room_id = this.get('parentView.parentView.channelId') || this.get('parentView.parentView.post.user.login') || null, - pbody = el && el.querySelector('.activity-body'); - - if ( ! message || ! el || ! pbody ) - return; - - // If this is for a specific emote set, only rerender if it matters. - if ( for_set && f.rooms && f.rooms[room_id] ) { - var sets = f.getEmotes(user_id, room_id); - if ( sets.indexOf(for_set) === -1 ) - return; - } - - var tokens = f.tokenize_feed_body(message, emotes, user_id, room_id), - output = f.render_tokens(tokens, true, false); - - pbody.innerHTML = '' + output + '
'; - } - }) -} \ No newline at end of file diff --git a/src/ember/following.js b/src/ember/following.js deleted file mode 100644 index 1d45d7c4..00000000 --- a/src/ember/following.js +++ /dev/null @@ -1,336 +0,0 @@ -var FFZ = window.FrankerFaceZ, - utils = require('../utils'), - constants = require('../constants'), - - createElement = utils.createElement, - - FOLLOWING_RE = /^\/kraken\/users\/([^/]+)\/follows\/channels/, - FOLLOWER_RE = /^\/kraken\/channels\/([^/]+)\/follows/; - - -// -------------------- -// Settings -// -------------------- - -FFZ.settings_info.enhance_profile_following = { - type: "boolean", - value: true, - - category: "Directory", - no_mobile: true, - - name: "Enhanced Following Control", - help: "Display additional controls on your own profile's Following tab to make management easier, as well as telling you how long everyone has been following everyone else in the profile." -} - - -// -------------------- -// Initialization -// -------------------- - -FFZ.prototype.setup_profile_following = function() { - if ( ! window.App ) - return; - - var f = this; - - // Build our is-following cache. - this._following_cache = {}; - this._follower_cache = {}; - - - // We want to hook the API to gather this information. It's easier than - // modifying the deserialization path. - var process_follows = function(channel_id, data, cache) { - f.log("Loading Follow Information for: " + channel_id, data); - - var user_cache = cache[channel_id] = cache[channel_id] || {}, - now = Date.now(); - - for(var i=0; i < data.length; i++) { - var follow = data[i], - user = follow && (follow.user || follow.channel); - - if ( ! user || ! user.name ) - continue; - - if ( user.display_name && user.display_name !== 'jtv' ) - FFZ.capitalization[user.name] = [user.display_name, now]; - - user_cache[user.name] = [ - follow.created_at ? utils.parse_date(follow.created_at) : null, - follow.notifications || false]; - } - }; - - - var ServiceAPI = utils.ember_lookup('service:api'); - if ( ServiceAPI ) - ServiceAPI.reopen({ - request: function(method, url, data, options) { - if ( method !== 'get' || url.indexOf('/kraken/') !== 0 ) - return this._super(method, url, data, options); - - var t = this; - return new Promise(function(success, fail) { - t._super(method, url, data, options).then(function(result) { - if ( result.follows ) { - var match = FOLLOWING_RE.exec(url); - if ( match ) - // Following Information - process_follows(match[1], result.follows, f._following_cache); - - match = FOLLOWER_RE.exec(url); - if ( match ) - // Follower Information - process_follows(match[1], result.follows, f._follower_cache); - } - - success(result); - - }).catch(function(err) { - fail(err); - }) - }); - } - }); - else - this.error("Unable to locate the Ember service:api"); - - - // Modify followed items. - //this.update_views('component:display-followed-item', this.modify_display_followed_item); - this.update_views('component:twitch-profile-card', this.modify_twitch_profile_card); -} - - -FFZ.prototype.modify_twitch_profile_card = function(component) { - var f = this; - utils.ember_reopen_view(component, { - ffzParentModel: function() { - var x = this.get('parentView'); - while(x) { - var model = x.get('model'); - if ( model ) - return model; - x = x.get('parentView'); - } - }.property('parentView'), - - ffz_init: function() { - var el = this.get('element'); - - el.classList.add('ffz-processed'); - jQuery('.aspect', el).zipsy(); - - if ( ! f.settings.enhance_profile_following ) - return; - - this.ffzUpdate(); - }, - - ffzUpdate: function() { - var el = this.get('element'), - t_el = el.querySelector('.ffz-followed-since'), - //notif_el = el.querySelector('.ffz-followed-notifications'), - - channel_id = this.get('ffzParentModel.model.id'), - is_following = this.get('ffzParentModel.relationshipName') === 'following', - - user = f.get_user(), - mine = user && user.login && user.login === channel_id, - big_cache = is_following ? f._following_cache : f._follower_cache, - user_cache = big_cache[channel_id] = big_cache[channel_id] || {}, - - user_id = this.get('channelInfo.id'), - data = user_cache[user_id]; - - //f.log("Profile Card [" + channel_id + "] " + user_id + " <" + JSON.stringify(data) + ">", this); - - if ( ! data || ! el ) { - if ( t_el ) - jQuery(t_el).remove(); - /*if ( notif_el ) - jQuery(notif_el).remove();*/ - return false; - } - - var now = Date.now() - (f._ws_server_offset || 0), - age = data[0] ? Math.floor((now - data[0].getTime()) / 1000) : 0, - t_el = el.querySelector('.ffz-followed-since') - - update_time = function() { - var data = user_cache[user_id], - now = Date.now() - (f._ws_server_offset || 0), - age = data && data[0] ? Math.floor((now - data[0].getTime()) / 1000) : undefined; - - if ( age !== undefined ) { - t_el.innerHTML = constants.CLOCK + ' ' + (age < 60 ? 'now' : utils.human_time(age, 10)); - t_el.title = 'Follow' + (is_following ? 'ed by ' : 'er of ') + channel_id + ' since:Minimum: 250
Default: 340", old_val, function(new_val) {
- if ( new_val === null || new_val === undefined )
- return;
-
- var width = parseInt(new_val);
- if ( ! width || Number.isNaN(width) || ! Number.isFinite(width) )
- width = 340;
-
- f.settings.set('right_column_width', Math.max(250, width));
- });
- },
-
- on_update: function(val) {
- if ( this.has_bttv )
- return;
-
- var Layout = utils.ember_lookup('service:layout');
- if ( ! Layout )
- return;
-
- Layout.set('rightColumnWidth', val);
- Ember.propertyDidChange(Layout, 'contentWidth');
- }
-};
-
-
-FFZ.settings_info.minimize_navigation = {
- type: "boolean",
- value: false,
-
- category: "Sidebar",
- no_mobile: true,
- no_bttv: true,
-
- name: "Minimize Navigation",
- help: "Slide the navigation bar mostly out of view when it's not being used.",
-
- on_update: function(val) {
- if ( this.has_bttv )
- return;
-
- var Layout = utils.ember_lookup('service:layout');
- if ( ! Layout )
- return;
-
- utils.toggle_cls('ffz-sidebar-minimize')(val);
- Layout.set('ffzMinimizeNavigation', val);
- //Ember.propertyDidChange(Layout, 'contentWidth');
- }
-}
-
-
-// --------------------
-// Initialization
-// --------------------
-
-FFZ.prototype.setup_layout = function() {
- if ( this.has_bttv )
- return;
-
- utils.toggle_cls("ffz-sidebar-swap")(this.settings.swap_sidebars);
- utils.toggle_cls('ffz-sidebar-minimize')(this.settings.minimize_navigation);
-
- this.log("Creating layout style element.");
- var s = this._layout_style = document.createElement('style');
- s.id = 'ffz-layout-css';
- document.head.appendChild(s);
-
- try {
- route_helper = window.require("web-client/utilities/route-matcher");
- } catch(err) {
- this.error("Unable to require the route-matcher utility.", err);
- }
-
- var Layout = utils.ember_lookup('service:layout'),
- LS = function(x) { return x },
- f = this;
-
- if ( ! Layout )
- return this.log("Unable to locate the Ember service:layout");
-
- try {
- LS = window.require("web-client/utilities/layout-scaling").scalePixelValue;
- } catch(err) { }
-
- this.log("Hooking the Ember service:layout");
-
- Layout.reopen({
- rightColumnWidth: 340,
- rawPortraitMode: 0,
-
- portraitVideoBelow: false,
-
- channelCoverHeight: function() {
- var setting = f.settings.hide_channel_banner,
- banner_hidden = setting === 1 ? f.settings.channel_bar_bottom : setting > 0;
-
- return ( banner_hidden || ! route_helper || route_helper.routeMatches && !route_helper.routeMatches(this.get('globals.currentPath'), route_helper.ROUTES.CHANNEL_ANY) ) ?
- 0 : 380;
-
- }.property("globals.currentPath"),
-
- portraitMode: function() {
- var raw = this.get("rawPortraitMode");
- this.set('portraitVideoBelow', raw === 3 || raw === 4);
-
- if ( raw === 0 )
- return false;
- if ( raw === 2 || raw === 4 )
- return true;
-
- // Not sure if I should be adding some other value to offset the ratio. What feels best?
- var ratio = this.get("windowWidth") / (this.get("windowHeight") + 120 + 60);
- return ratio < 1;
-
- }.property("rawPortraitMode", "windowHeight", "windowWidth"),
-
- isTooSmallForRightColumn: function() {
- if ( ! f.has_bttv && this.get('portraitMode') ) {
- var size = this.get('fullSizePlayerDimensions'),
- extra = this.get('ffzExtraHeight'),
- height = size.height + extra;
-
- // Make sure we have at least a bit of room for the chat.
- return this.get("windowHeight") < height;
-
- } else
- return this.get("windowWidth") < (1090 - this.get('rightColumnWidth'))
-
- }.property("ffzExtraHeight", "windowWidth", "rightColumnWidth", "fullSizePlayerDimensions", "windowHeight"),
-
- contentWidth: function() {
- var left_width = LS(f.settings.socialbar_hide ? 0 : this.get('isSocialColumnCollapsed') ? 50 : 240),
- right_width = ! f.has_bttv && this.get('portraitMode') ? 0 : this.get("isRightColumnClosed") ? 0 : this.get("rightColumnWidth");
-
- return this.get("windowWidth") - left_width - right_width - LS(60);
-
- }.property("windowWidth", 'ffzMinimizeNavigation', "portraitMode", "isRightColumnClosed", "rightColumnWidth", "isSocialColumnCollapsed"),
-
- ffzExtraHeight: function() {
- return (this.get('ffzMinimizeNavigation') ? 10 : 50) +
- (f.settings.channel_bar_collapse ? 10 : 60) + 15 +
- (f.settings.channel_title_top === 2 ? 20 : f.settings.channel_title_top > 0 ? 55 : 0) +
- (f.settings.channel_title_top ? 70 : 80);
- }.property("ffzMinimizeNavigation"),
-
- fullSizePlayerDimensions: function() {
- var h = this.get('windowHeight'),
- c = this.get('PLAYER_CONTROLS_HEIGHT'),
- r = this.get('contentWidth'),
-
- extra_height = this.get('ffzExtraHeight'),
- extra_theater_height = 120 + (f.settings.channel_bar_collapse ? 10 : 60) + 40,
-
- i = Math.round(9 * r / 16) + c,
- d = h - extra_height,
- e = h - extra_theater_height,
-
- l = Math.floor(r),
- o = Math.floor(Math.min(i, d)),
- s = Math.floor(Math.min(i, e));
-
- return {
- width: l,
- height: o,
- targetHeight: s
- }
-
- }.property("ffzExtraHeight", "contentWidth", "windowHeight", "portraitMode", "PLAYER_CONTROLS_HEIGHT"),
-
- playerStyle: function() {
- var size = this.get('fullSizePlayerDimensions');
-
- return '');
-
- }.property('badgeSet', 'ffzBadgeSet'),
-
- ffzFreezeUpdateBuffer: function(val) {
- if ( val === undefined )
- val = this.get('stuckToBottom');
-
- VODService && VODService.set("messageBufferSize", f.settings.scrollback_length + (val ? 0 : 150));
- },
-
- _scheduleScrollToBottom: function() {
- this._scrollToBottom();
- }
-
- }, FFZ.HoverPause));
-}
\ No newline at end of file
diff --git a/src/ember/wrapper.js b/src/ember/wrapper.js
deleted file mode 100644
index 805b77e6..00000000
--- a/src/ember/wrapper.js
+++ /dev/null
@@ -1,99 +0,0 @@
-var FFZ = window.FrankerFaceZ,
- utils = require("../utils"),
- constants = require("../constants");
-
-// --------------------
-// Initialization
-// --------------------
-
-FFZ.prototype.setup_ember_wrapper = function() {
- this._views_to_update = [];
- this._ember_finalized = false;
-}
-
-
-FFZ.prototype.update_views = function(klass, modifier, if_not_exists, immediate, no_modify_existing) {
- var original_klass;
- if ( typeof klass === 'string' ) {
- original_klass = klass;
- klass = utils.ember_resolve(klass);
- if ( ! klass && if_not_exists ) {
- if ( typeof if_not_exists === "function" )
- if_not_exists.call(this, klass, modifier);
- else {
- klass = Ember.Component.extend({});
- App.__registry__.register(original_klass, klass);
- }
- }
-
- if ( ! klass ) {
- this.error("Unable to locate the Ember " + original_klass);
- return false;
- }
- } else
- original_klass = klass.toString();
-
- if ( this._ember_finalized || immediate || ! this._views_to_update )
- this._update_views([[original_klass, klass, modifier, no_modify_existing || false]]);
- else
- this._views_to_update.push([original_klass, klass, modifier, no_modify_existing || false]);
-
- return true;
-}
-
-
-FFZ.prototype.finalize_ember_wrapper = function() {
- this._ember_finalized = true;
- var views = this._views_to_update;
- this._views_to_update = null;
- this._update_views(views);
-}
-
-
-FFZ.prototype._update_views = function(klasses) {
- this.log("Updating Ember classes and instances.", klasses);
- var updated_instances = 0,
- updated_klasses = 0;
-
- // Modify all pending classes and clear them from cache.
- for(var i=0; i < klasses.length; i++) {
- klasses[i][2].call(this, klasses[i][1]);
- updated_klasses++;
-
- try {
- klasses[i][1].create().destroy()
- } catch(err) {
- if ( constants.DEBUG )
- this.log("There was an error creating and destroying an instance of the Ember class \"" + klasses[i][0] + "\" to clear its cache.", err);
- }
- }
-
- // Iterate over all existing views and update them as necessary.
- var views = utils.ember_views();
- for(var view_id in views) {
- var view = views[view_id];
- if ( ! view )
- continue;
-
- for(var i=0; i < klasses.length; i++)
- if ( view instanceof klasses[i][1] ) {
- updated_instances++;
-
- try {
- if ( ! view.ffz_modified && ! klasses[i][3] )
- klasses[i][2].call(this, view);
-
- var func = view.ffz_update || view.ffz_init;
- if ( func )
- func.call(view);
-
- } catch(err) {
- this.error("An error occured when updating an existing Ember instance of: " + klasses[i][0], err);
- }
-
- break;
- }
- }
-
- this.log("Updated " + utils.number_commas(updated_instances) + " existing instances across " + updated_klasses + " classes.");
-}
\ No newline at end of file
diff --git a/src/emoticons.js b/src/emoticons.js
deleted file mode 100644
index 844d64cd..00000000
--- a/src/emoticons.js
+++ /dev/null
@@ -1,572 +0,0 @@
-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*['"]([^'"]+)['"][^}]+(?:}|$)/,
- constants = require('./constants'),
- utils = require('./utils'),
-
- IS_OSX = constants.IS_OSX,
-
- MODIFIERS = {
- 59847: {
- modifier_offset: '0 15px 15px 0',
- modifier: true
- },
-
- 70852: {
- modifier: true,
- modifier_offset: '0 5px 20px 0',
- extra_width: 5,
- shrink_to_fit: true
- },
-
- 70854: {
- modifier: true,
- modifier_offset: '30px 0 0'
- },
-
- 147049: {
- modifier: true,
- modifier_offset: '4px 1px 0 3px'
- },
-
- 147011: {
- modifier: true,
- modifier_offset: '0'
- },
-
- 70864: {
- modifier: true,
- modifier_offset: '0'
- },
-
- 147038: {
- modifier: true,
- modifier_offset: '0'
- }
- };
-
-
-// ---------------------
-// Initialization
-// ---------------------
-
-FFZ.prototype.setup_emoticons = function() {
- this.log("Preparing emoticon system.");
-
- // Usage Data
- this.emote_usage = {};
-
- this.log("Creating emoticon style element.");
- var s = this._emote_style = document.createElement('style');
- s.id = "ffz-emoticon-css";
- document.head.appendChild(s);
-
- this.log("Generating CSS for existing API emoticon sets.");
- for(var set_id in this.emote_sets) {
- var es = this.emote_sets[set_id];
- if ( es && es.pending_css ) {
- utils.update_css(s, set_id, es.pending_css + (es.css || ''));
- es.pending_css = null;
- }
- }
-
- this.log("Loading global emote sets.");
- this.load_global_sets();
-
- this.log("Loading emoji data.");
- this.load_emoji_data();
-
- //this.log("Watching Twitch emoticon parser to ensure it loads.");
- //this._twitch_emote_check = setTimeout(this.check_twitch_emotes.bind(this), 10000);
-
- this._twitch_inventory_sets = [];
- this.refresh_twitch_inventory();
-}
-
-
-FFZ.prototype.refresh_twitch_inventory = function() {
- var f = this;
- return new Promise(function(succeed) {
- var o = [],
- user = f.get_user();
-
- if ( ! user ) {
- f._twitch_inventory_sets = o;
- return succeed(o);
- }
-
- utils.api.get("/v5/inventory/emoticons", null, {version: 5}, user.chat_oauth_token)
- .done(function(data) {
-
- o = Object.keys(data.emoticon_sets || {});
- var ets = f._twitch_emote_to_set = f._twitch_emote_to_set || {};
-
- for(var i=0; i < o.length; i++) {
- var set_id = o[i],
- emotes = data.emoticon_sets[set_id];
- for(var j=0; j < emotes.length; j++)
- ets[emotes[j].id] = set_id;
- }
-
- f._twitch_inventory_sets = o;
- return succeed(o);
-
- }).fail(function() {
- f._twitch_inventory_sets = o;
- return succeed(o);
- });
- });
-}
-
-
-// ------------------------
-// Emote Usage
-// ------------------------
-
-FFZ.prototype.add_usage = function(room_id, emote, count) {
- // Only report usage from FFZ emotes. Not extensions to FFZ.
- var emote_set = this.emote_sets[emote.set_id];
- if ( ! emote_set || emote_set.source_ext )
- return;
-
- var emote_id = emote.id,
- rooms = this.emote_usage[emote_id] = this.emote_usage[emote_id] || {};
-
- rooms[room_id] = (rooms[room_id] || 0) + (count || 1);
-
- if ( this._emote_report_scheduled )
- return;
-
- this._emote_report_scheduled = setTimeout(this._report_emotes.bind(this), 30000);
-}
-
-
-FFZ.prototype._report_emotes = function() {
- if ( this._emote_report_scheduled )
- delete this._emote_report_scheduled;
-
- var usage = this.emote_usage;
- this.emote_usage = {};
- this.ws_send("emoticon_uses", [usage], function(){}, true);
-}
-
-
-// ------------------------
-// Emote Click Handler
-// ------------------------
-
-FFZ.prototype._click_emote = function(target, event) {
- if ( ! target || ! target.classList.contains('emoticon') )
- return;
-
- if ( event && ! event.shiftKey && ! event.shiftLeft && ((!IS_OSX && event.ctrlKey) || (IS_OSX && event.metaKey)) ) {
- var eid, favorite_key;
-
- if ( target.classList.contains('emoji') ) {
- var emoji = target.getAttribute('data-ffz-emoji'),
- emoji_data = this.emoji_data[emoji];
-
- if ( emoji_data ) {
- favorite_key = 'emoji';
- eid = emoji_data.raw;
- }
-
- } else {
- eid = target.getAttribute('data-emote');
-
- if ( eid ) {
- eid = parseInt(eid);
- var twitch_set = this._twitch_emote_to_set[eid];
- if ( twitch_set )
- if ( this._twitch_inventory_sets.indexOf(twitch_set) !== -1 )
- favorite_key = 'twitch-inventory';
- else
- favorite_key = 'twitch-' + twitch_set;
-
- } else {
- eid = target.getAttribute('data-ffz-emote');
- var set_id = target.getAttribute('data-ffz-set'),
- emote_set = set_id && this.emote_sets[set_id];
-
- if ( emote_set && emote_set.emoticons && emote_set.emoticons[eid] ) {
- favorite_key = 'ffz-' + (emote_set.hasOwnProperty('source_ext') ? 'ext-' + emote_set.source_ext + '-' + emote_set.source_id : set_id);
- eid = emote_set.emoticons[eid].id || eid;
- }
- }
- }
-
- if ( favorite_key ) {
- var favs = this.settings.favorite_emotes[favorite_key] = this.settings.favorite_emotes[favorite_key] || [],
- is_favorited = favs.indexOf(eid) !== -1;
-
- if ( is_favorited )
- favs.removeObject(eid);
- else
- favs.push(eid);
-
- this.settings.set("favorite_emotes", this.settings.favorite_emotes, true);
- jQuery(target).trigger('mouseout').trigger('mouseover');
- this._inputv && this._inputv.propertyDidChange('ffz_emoticons');
- }
-
- return true;
- }
-
- if ( ! this.settings.clickable_emoticons || (event && !(event.shiftKey || event.shiftLeft)) )
- return;
-
- var eid = target.getAttribute('data-emote');
- if ( eid )
- window.open("https://twitchemotes.com/emote/" + eid);
- else {
- eid = target.getAttribute("data-ffz-emote");
- var es = target.getAttribute("data-ffz-set"),
- emote_set = es && this.emote_sets[es],
- url;
-
- if ( ! emote_set )
- return;
-
- if ( emote_set.hasOwnProperty('source_ext') ) {
- var api = this._apis[emote_set.source_ext];
- if ( api && api.emote_url_generator )
- url = api.emote_url_generator(emote_set.source_id, eid);
- } else
- url = "https://www.frankerfacez.com/emoticons/" + eid;
-
- if ( url ) {
- window.open(url);
- return true;
- }
- }
-}
-
-
-// ------------------------
-// Twitch Emoticon Checker
-// ------------------------
-
-/*FFZ.prototype.check_twitch_emotes = function() {
- if ( this._twitch_emote_check ) {
- clearTimeout(this._twitch_emote_check);
- delete this._twitch_emote_check;
- }
-
- var room;
- if ( this.rooms ) {
- for(var key in this.rooms) {
- if ( this.rooms.hasOwnProperty(key) ) {
- room = this.rooms[key];
- break;
- }
- }
- }
-
- if ( ! room || ! room.room || ! room.room.tmiSession ) {
- this._twitch_emote_check = setTimeout(this.check_twitch_emotes.bind(this), 10000);
- return;
- }
-
- var parser = room.room.tmiSession._emotesParser,
- emotes = Object.keys(parser.emoticonRegexToIds).length;
-
- // If we have emotes, we're done!
- if ( emotes > 0 )
- return;
-
- // No emotes. Try loading them.
- var sets = parser.emoticonSetIds;
- parser.emoticonSetIds = "";
- parser.updateEmoticons(sets);
-
- // Check again in a bit to see if we've got them.
- this._twitch_emote_check = setTimeout(this.check_twitch_emotes.bind(this), 10000);
-}*/
-
-
-
-// ---------------------
-// Set Management
-// ---------------------
-
-FFZ.prototype.getEmotes = function(user_id, room_id) {
- var user = this.users && this.users[user_id],
- room = this.rooms && this.rooms[room_id],
- room_user = room && room.users && room.users[user_id];
-
- return _.union(user && user.sets || [], room_user && room_user.sets || [], room && room.set && [room.set] || [], room && room.extra_sets || [], room && room.ext_sets || [], this.default_sets);
-}
-
-
-// ---------------------
-// Commands
-// ---------------------
-
-FFZ.ws_commands.reload_set = function(set_id) {
- if ( this.emote_sets.hasOwnProperty(set_id) )
- this.load_set(set_id);
-}
-
-
-FFZ.ws_commands.load_set = function(set_id) {
- this.load_set(set_id);
-}
-
-
-// ---------------------
-// Emoji Loading
-// ---------------------
-
-FFZ.prototype.load_emoji_data = function(callback, tries) {
- var f = this,
- puny = window.punycode && punycode.ucs2;
-
- jQuery.getJSON(constants.SERVER + "emoji/emoji-data.json")
- .done(function(data) {
- var new_data = {},
- by_name = {};
- for(var eid in data) {
- var emoji = data[eid];
- eid = eid.toLowerCase();
- emoji.code = eid;
-
- new_data[eid] = emoji;
- if ( emoji.short_name )
- by_name[emoji.short_name] = eid;
- if ( emoji.names && emoji.names.length )
- for(var x=0,y=emoji.names.length; x < y; x++)
- by_name[emoji.names[x]] = eid;
-
- emoji.raw = _.map(emoji.code.split("-"), utils.codepoint_to_emoji).join("");
-
- emoji.tw_src = constants.SERVER + 'emoji/tw/' + eid + '.svg';
- emoji.noto_src = constants.SERVER + 'emoji/noto-' + eid + '.svg';
- emoji.one_src = constants.SERVER + 'emoji/one/' + eid + '.svg';
-
- emoji.token = {
- type: "emoticon",
- imgSrc: true,
-
- length: puny ? puny.decode(emoji.raw).length : emoji.raw.length,
-
- tw_src: emoji.tw_src,
- noto_src: emoji.noto_src,
- one_src: emoji.one_src,
-
- tw: emoji.tw,
- noto: emoji.noto,
- one: emoji.one,
-
- ffzEmoji: eid,
- altText: emoji.raw
- };
- }
-
- f.emoji_data = new_data;
- f.emoji_names = by_name;
-
- f.log("Loaded data on " + Object.keys(new_data).length + " emoji.");
- if ( typeof callback === "function" )
- callback(true, data);
-
- }).fail(function(data) {
- if ( data.status === 404 )
- return typeof callback === "function" && callback(false);
-
- tries = (tries || 0) + 1;
- if ( tries < 50 )
- return f.load_emoji(callback, tries);
-
- return typeof callback === "function" && callback(false);
- });
-}
-
-
-// ---------------------
-// Set Loading
-// ---------------------
-
-FFZ.prototype.load_global_sets = function(callback, tries) {
- var f = this;
- jQuery.getJSON(constants.API_SERVER + "v1/set/global")
- .done(function(data) {
- // Apply default sets.
- var ds = f.default_sets,
- gs = f.global_sets,
- sets = data.sets || {};
-
- // Remove non-API sets from default and global sets.
- for(var i=ds.length; i--; ) {
- var set_id = ds[i];
- if ( data.default_sets.indexOf(set_id) === -1 && (!f.feature_friday || f.feature_friday.set !== set_id) && (! f.emote_sets[set_id] || ! f.emote_sets[set_id].source_ext) )
- ds.splice(i, 1);
- }
-
- for(var i=0; i < data.default_sets.length; i++) {
- var set_id = data.default_sets[i];
- if ( ds.indexOf(set_id) === -1 )
- ds.push(set_id);
- }
-
- for(var i=gs.length; i--; ) {
- var set_id = gs[i];
- if ( ! sets[set_id] && (!f.feature_friday || f.feature_friday.set !== set_id) && (! f.emote_sets[set_id] || ! f.emote_sets[set_id].source_ext) )
- gs.splice(i, 1);
- }
-
- for(var set_id in sets) {
- var set = sets[set_id];
- if ( gs.indexOf(set_id) === -1 )
- gs.push(set_id);
-
- f._load_set_json(set_id, undefined, set);
- }
-
- f._load_set_users(data.users);
-
- }).fail(function(data) {
- if ( data.status == 404 )
- return typeof callback == "function" && callback(false);
-
- tries = tries || 0;
- tries++;
- if ( tries < 50 )
- return f.load_global_sets(callback, tries);
-
- return typeof callback == "function" && callback(false);
- });
-}
-
-
-FFZ.prototype._load_set_users = function(data) {
- if ( data )
- for(var set_id in data)
- if ( data.hasOwnProperty(set_id) ) {
- var emote_set = this.emote_sets[set_id],
- users = data[set_id];
-
- for(var i=0; i < users.length; i++) {
- var user = users[i],
- ud = this.users[user] = this.users[user] || {},
- sets = ud.sets = ud.sets || [];
-
- if ( sets.indexOf(set_id) === -1 )
- sets.push(set_id);
- }
-
- this.log('Added "' + (emote_set ? emote_set.title : set_id) + '" emote set to ' + utils.number_commas(users.length) + ' users.');
- }
-}
-
-
-FFZ.prototype.load_set = function(set_id, callback, tries) {
- var f = this;
- jQuery.getJSON(constants.API_SERVER + "v1/set/" + set_id)
- .done(function(data) {
- f._load_set_json(set_id, callback, data && data.set);
- f._load_set_users(data.users);
-
- }).fail(function(data) {
- if ( data.status == 404 )
- return typeof callback == "function" && callback(false);
-
- tries = tries || 0;
- tries++;
- if ( tries < 10 )
- return f.load_set(set_id, callback, tries);
-
- return typeof callback == "function" && callback(false);
- });
-}
-
-
-FFZ.prototype.unload_set = function(set_id) {
- var set = this.emote_sets[set_id];
- if ( ! set )
- return;
-
- this.log("Unloading emoticons for set: " + set_id);
-
- utils.update_css(this._emote_style, set_id, null);
- delete this.emote_sets[set_id];
-
- if ( set.hasOwnProperty('source_ext') ) {
- var api = this._apis[set.source_ext];
- if ( api && api.emote_sets && api.emote_sets[set_id] )
- api.emote_sets[set_id] = undefined;
- }
-
- if ( this._inputv )
- Ember.propertyDidChange(this._inputv, 'ffz_emoticons');
-}
-
-
-FFZ.prototype._load_set_json = function(set_id, callback, data) {
- if ( ! data )
- return typeof callback == "function" && callback(false);
-
- // Do we have existing users?
- var users = this.emote_sets[set_id] && this.emote_sets[set_id].users || [];
-
- // Store our set.
- this.emote_sets[set_id] = data;
- data.users = users;
- data.count = 0;
-
-
- // Iterate through all the emoticons, building CSS and regex objects as appropriate.
- var output_css = "",
- ems = data.emoticons;
-
- data.emoticons = {};
-
- for(var i=0; i < ems.length; i++) {
- var emote = ems[i];
-
- //emote.klass = "ffz-emote-" + emote.id;
- emote.set_id = set_id;
-
- emote.srcSet = emote.urls[1] + " 1x";
- if ( emote.urls[2] )
- emote.srcSet += ", " + emote.urls[2] + " 2x";
- if ( emote.urls[4] )
- emote.srcSet += ", " + emote.urls[4] + " 4x";
-
- if ( emote.name[emote.name.length-1] === "!" )
- emote.regex = new RegExp("(^|\\W|\\b)(" + utils.escape_regex(emote.name) + ")(?=\\W|$)", "g");
- else
- emote.regex = new RegExp("(^|\\W|\\b)(" + utils.escape_regex(emote.name) + ")\\b", "g");
-
- emote.token = {
- type: "emoticon",
- srcSet: emote.srcSet,
- imgSrc: emote.urls[1],
- ffzEmote: emote.id,
- ffzEmoteSet: set_id,
- altText: emote.hidden ? '???' : emote.name
- };
-
- if ( MODIFIERS.hasOwnProperty(emote.id) )
- emote = _.extend(emote, MODIFIERS[emote.id]);
-
- output_css += utils.emote_css(emote);
- data.count++;
- data.emoticons[emote.id] = emote;
- }
-
- utils.update_css(this._emote_style, set_id, output_css + (data.css || ""));
- this.log("Updated emoticons for set #" + set_id + ": " + data.title, data);
-
- if ( this._cindex )
- this._cindex.ffzFixTitle();
-
- this.update_ui_link();
-
- if ( this._inputv )
- Ember.propertyDidChange(this._inputv, 'ffz_emoticons');
-
- this.rerender_feed_cards(set_id);
-
- if ( callback )
- callback(true, data);
-}
\ No newline at end of file
diff --git a/src/entry.js b/src/entry.js
new file mode 100644
index 00000000..c0840920
--- /dev/null
+++ b/src/entry.js
@@ -0,0 +1,16 @@
+/* eslint strict: off */
+'use strict';
+(() => {
+ if ( location.hostname === 'player.twitch.tv' )
+ return;
+
+ const DEBUG = localStorage.ffzDebugMode == 'true' && document.body.classList.contains('ffz-dev'),
+ SERVER = DEBUG ? '//localhost:8000' : '//cdn.frankerfacez.com',
+ FLAVOR = window.Ember ? 'umbral' : 'avalon',
+
+ script = document.createElement('script');
+
+ script.id = 'ffz-script';
+ script.src = `${SERVER}/script/${FLAVOR}.js?_=${Date.now()}`;
+ document.head.appendChild(script);
+})();
\ No newline at end of file
diff --git a/src/ext/api.js b/src/ext/api.js
deleted file mode 100644
index 573ac011..00000000
--- a/src/ext/api.js
+++ /dev/null
@@ -1,682 +0,0 @@
-var FFZ = window.FrankerFaceZ,
- utils = require('../utils'),
- constants = require('../constants');
-
-
-// ---------------------
-// Badware Check
-// ---------------------
-
-FFZ.prototype.check_badware = function() {
- if ( this.embed_in_dash || ! window.jQuery || ! window.jQuery.noty )
- return;
-
- // Check for the stolen version of BTTV4FFZ.
- if ( FFZ.settings_info.bttv_global_emotes && FFZ.settings_info.bttv_global_emotes.category === "BetterTTV" ) {
- var shown = localStorage.ffz_warning_bttv4ffz_clone;
- if ( shown !== "true" ) {
- localStorage.ffz_warning_bttv4ffz_clone = "true";
- this.show_message("You appear to be using an unofficial version of BTTV4FFZ that was copied without the developer's permission. Please use the official version available at https://lordmau5.com/bttv4ffz/");
- }
- }
-}
-
-
-// ---------------------
-// API Constructor
-// ---------------------
-
-var API = FFZ.API = function(instance, name, icon, version, name_key) {
- this.ffz = instance || FFZ.get();
-
- // Check for a known API!
- if ( name ) {
- for(var id in this.ffz._known_apis) {
- if ( this.ffz._known_apis[id] === name ) {
- this.id = id;
- break;
- }
- }
- }
-
- if ( ! this.id ) {
- var i = 0;
- while( ! this.id ) {
- if ( ! this.ffz._known_apis.hasOwnProperty(i) ) {
- this.id = i;
- break;
- }
- i++;
- }
-
- if ( name ) {
- this.ffz._known_apis[this.id] = name;
- localStorage.ffz_known_apis = JSON.stringify(this.ffz._known_apis);
- }
- }
-
- this._events = {};
-
- this.ffz._apis[this.id] = this;
-
- this.emote_sets = {};
- this.global_sets = [];
- this.default_sets = [];
-
- this.badges = {};
-
- this.users = {};
-
- this.name = name || ("Extension#" + this.id);
- this.name_key = name_key || this.name.replace(/[^A-Z0-9_\-]/g, '').toLowerCase();
-
- // We can't start name key with a number.
- if ( /^[0-9]/.test(this.name_key) )
- this.name_key = '_' + this.name_key;
-
- this.icon = icon || null;
- this.version = version || null;
-
- this.ffz.log('Registered New Extension #' + this.id + ' (' + this.name_key + '): ' + this.name);
-};
-
-
-FFZ.prototype.api = function(name, icon, version, name_key) {
- // Load the known APIs list.
- if ( ! this._known_apis ) {
- this._known_apis = {};
- var stored_val = localStorage.getItem("ffz_known_apis");
- if ( stored_val !== null )
- try {
- this._known_apis = JSON.parse(stored_val);
- } catch(err) {
- this.error("Error loading Known APIs", err);
- }
- }
-
- return new API(this, name, icon, version, name_key);
-}
-
-
-API.prototype.log = function(msg, data, to_json, log_json) {
- this.ffz.log('Ext #' + this.id + ' (' + this.name_key + '): ' + msg, data, to_json, log_json);
-}
-
-
-API.prototype.error = function(msg, error, to_json, log_json) {
- this.ffz.error('Ext #' + this.id + ' (' + this.name_key + '): ' + msg, error, to_json, log_json);
-}
-
-
-// ---------------------
-// Events
-// ---------------------
-
-API.prototype.on = function(event, func) {
- var e = this._events[event] = this._events[event] || [];
- if ( e.indexOf(func) === -1 )
- e.push(func);
-}
-
-
-API.prototype.off = function(event, func) {
- if ( func === undefined )
- this._events[event] = [];
- else {
- var e = this._events[event] = this._events[event] || [],
- ind = e.indexOf(func);
- if ( ind !== -1 )
- e.splice(ind, 1);
- }
-}
-
-
-var slice = Array.prototype.slice;
-
-API.prototype.trigger = function(event /*, args... */) {
- var e = this._events[event];
- if ( e && e.length )
- for(var i=0; i < e.length; i++)
- e[i].apply(this, slice.call(arguments, 1));
-}
-
-
-FFZ.prototype.api_trigger = function(/*event, args...*/) {
- for(var api_id in this._apis) {
- var api = this._apis[api_id];
- api.trigger.apply(api, arguments);
- }
-}
-
-
-// ---------------------
-// Metadata~!
-// ---------------------
-
-API.prototype.register_metadata = function(key, data) {
- data.source_ext = this.id;
- data.source_id = key;
- FFZ.channel_metadata[this.id + '-' + key] = data;
- this.update_metadata(key);
-}
-
-
-API.prototype.unregister_metadata = function(key) {
- delete FFZ.channel_metadata[this.id + '-' + key];
- this.update_metadata(key, true);
-}
-
-
-API.prototype.update_metadata = function(key, full_update) {
- var real_key = this.id + '-' + key,
- channel = this.ffz._cindex;
-
- if ( channel ) {
- if ( full_update )
- channel.$('.cn-metabar__ffz[data-key="' + real_key + '"]').remove();
-
- channel.ffzUpdateMetadata(real_key);
- }
-}
-
-
-// ---------------------
-// Set Loading
-// ---------------------
-
-API.prototype._load_set = function(real_id, set_id, data) {
- if ( ! data )
- return null;
-
- // Check for an existing set to copy the users.
- var users = [];
- if ( this.emote_sets[set_id] && this.emote_sets[set_id].users )
- users = this.emote_sets[set_id].users;
-
- var emote_set = _.extend({
- source: this.name,
- icon: this.icon || null,
- title: "Global Emoticons",
- _type: 0
- }, data, {
- source_ext: this.id,
- source_id: set_id,
- id: real_id,
- users: users,
- count: 0,
- emoticons: {},
- });
-
- this.emote_sets[set_id] = emote_set;
-
- // Use the real ID for FFZ's own tracking.
- if ( this.ffz.emote_sets )
- this.ffz.emote_sets[real_id] = emote_set;
-
- var output_css = "",
- ems = data.emoticons,
- emoticons = emote_set.emoticons;
-
- for(var i=0; i < ems.length; i++) {
- var emote = ems[i],
- id = emote.id || (this.name + '-' + set_id + '-' + i);
-
- if ( ! emote.name )
- continue;
-
- /*var new_emote = _.extend({}, emote, {
- id: id,
- set_id: real_id,
- srcSet: emote.urls[1] + ' 1x'
- });*/
-
- emote.id = id;
- emote.set_id = real_id;
- emote.srcSet = emote.urls[1] + ' 1x';
-
- if ( emote.urls[2] )
- /*new_*/emote.srcSet += ', ' + emote.urls[2] + ' 2x';
-
- if ( emote.urls[3] )
- /*new_*/emote.srcSet += ', ' + emote.urls[3] + ' 3x';
-
- if ( emote.urls[4] )
- /*new_*/emote.srcSet += ', ' + emote.urls[4] + ' 4x';
-
- /*new_*/emote.token = {
- type: "emoticon",
- srcSet: /*new_*/emote.srcSet,
- imgSrc: /*new_*/emote.urls[1],
- ffzEmote: id,
- ffzEmoteSet: real_id,
- altText: /*new_*/emote.hidden ? '???' : /*new_*/emote.name
- };
-
- output_css += utils.emote_css(/*new_*/emote);
- emote_set.count++;
- emoticons[id] = /*new_*/emote;
- }
-
- // Use the real ID for building CSS.
- if ( this.ffz._emote_style )
- utils.update_css(this.ffz._emote_style, real_id, output_css + (emote_set.css || ""));
- else
- emote_set.pending_css = output_css;
-
- if ( this.ffz._cindex )
- this.ffz._cindex.ffzFixTitle();
-
- try {
- this.ffz.update_ui_link();
- } catch(err) { }
-
- return emote_set;
-}
-
-
-// -------------------------
-// Loading / Unloading Sets
-// -------------------------
-
-API.prototype.load_set = function(set_id, emote_set) {
- var exact_id = this.id + '-' + set_id,
- already_loaded = this.emote_sets[set_id];
-
- emote_set.title = emote_set.title || "Global Emoticons";
- emote_set._type = emote_set._type || 0;
-
- emote_set = this._load_set(exact_id, set_id, emote_set);
-
- // Avoid spamming the console if and when other extensions
- // spend time constantly updating emote sets.
- if ( this.log_sets )
- this.log("Loaded Emoticon Set #" + set_id + ": " + emote_set.title + " (" + emote_set.count + " emotes)", emote_set);
-
- return emote_set;
-}
-
-
-API.prototype.unload_set = function(set_id) {
- var exact_id = this.id + '-' + set_id,
- emote_set = this.emote_sets[set_id];
-
- if ( ! emote_set )
- return;
-
- // First, let's unregister it as a global.
- this.unregister_global_set(set_id);
-
- // Now, remove the set data.
- if ( this.ffz._emote_style )
- utils.update_css(this.ffz._emote_style, exact_id, null);
-
- this.emote_sets[set_id] = undefined;
- if ( this.ffz.emote_sets )
- this.ffz.emote_sets[exact_id] = undefined;
-
- // Remove from all its Rooms
- if ( emote_set.users ) {
- for(var i=0; i < emote_set.users.length; i++) {
- var room_id = emote_set.users[i],
- room = this.ffz.rooms && this.ffz.rooms[room_id];
-
- if ( ! room )
- continue;
-
- var ind = room.ext_sets ? room.ext_sets.indexOf(exact_id) : -1;
- if ( ind !== -1 )
- room.ext_sets.splice(ind,1);
- }
-
- emote_set.users = [];
- }
-
-
- return emote_set;
-}
-
-
-API.prototype.get_set = function(set_id) {
- return this.emote_sets[set_id];
-}
-
-
-// ---------------------
-// Global Emote Sets
-// ---------------------
-
-API.prototype.register_global_set = function(set_id, emote_set) {
- var exact_id = this.id + '-' + set_id;
-
- if ( emote_set ) {
- // If a set was provided, load it.
- emote_set = this.load_set(set_id, emote_set);
- } else
- emote_set = this.emote_sets[set_id];
-
- if ( ! emote_set )
- throw new Error("Invalid set ID");
-
-
- // Make sure the set is still available with FFZ.
- if ( this.ffz.emote_sets && ! this.ffz.emote_sets[exact_id] )
- this.ffz.emote_sets[exact_id] = emote_set;
-
-
- // It's a valid set if we get here, so make it global.
- if ( this.global_sets.indexOf(set_id) === -1 )
- this.global_sets.push(set_id);
-
- if ( this.default_sets.indexOf(set_id) === -1 )
- this.default_sets.push(set_id);
-
- if ( this.ffz.global_sets && this.ffz.global_sets.indexOf(exact_id) === -1 )
- this.ffz.global_sets.push(exact_id);
-
- if ( this.ffz.default_sets && this.ffz.default_sets.indexOf(exact_id) === -1 )
- this.ffz.default_sets.push(exact_id);
-
- // Update tab completion.
- if ( this.ffz._inputv )
- Ember.propertyDidChange(this.ffz._inputv, 'ffz_emoticons');
-};
-
-
-API.prototype.unregister_global_set = function(set_id) {
- var exact_id = this.id + '-' + set_id,
- emote_set = this.emote_sets[set_id];
-
- if ( ! emote_set )
- return;
-
- // Remove the set from global sets.
- var ind = this.global_sets.indexOf(set_id);
- if ( ind !== -1 )
- this.global_sets.splice(ind,1);
-
- ind = this.default_sets.indexOf(set_id);
- if ( ind !== -1 )
- this.default_sets.splice(ind,1);
-
- ind = this.ffz.global_sets ? this.ffz.global_sets.indexOf(exact_id) : -1;
- if ( ind !== -1 )
- this.ffz.global_sets.splice(ind,1);
-
- ind = this.ffz.default_sets ? this.ffz.default_sets.indexOf(exact_id) : -1;
- if ( ind !== -1 )
- this.ffz.default_sets.splice(ind,1);
-
- // Update tab completion.
- if ( this.ffz._inputv )
- Ember.propertyDidChange(this.ffz._inputv, 'ffz_emoticons');
-};
-
-
-// -----------------------
-// Per-Channel Emote Sets
-// -----------------------
-
-API.prototype.register_room_set = function(room_id, set_id, emote_set) {
- var exact_id = this.id + '-' + set_id,
- room = this.ffz.rooms && this.ffz.rooms[room_id];
-
- if ( ! room )
- throw new Error("Room not loaded");
-
- if ( emote_set ) {
- // If a set was provided, load it.
- emote_set.title = emote_set.title || "Channel: " + (room.display_name || room_id);
- emote_set._type = emote_set._type || 1;
-
- emote_set = this.load_set(set_id, emote_set);
- } else
- emote_set = this.emote_sets[set_id];
-
- if ( ! emote_set )
- throw new Error("Invalid set ID");
-
- // Make sure the set is still available with FFZ.
- if ( ! this.ffz.emote_sets[exact_id] )
- this.ffz.emote_sets[exact_id] = emote_set;
-
- // Register it on the room.
- if ( room.ext_sets && room.ext_sets.indexOf(exact_id) === -1 )
- room.ext_sets.push(exact_id);
- if ( emote_set.users.indexOf(room_id) === -1 )
- emote_set.users.push(room_id);
-
- // Update tab completion.
- if ( this.ffz._inputv )
- Ember.propertyDidChange(this.ffz._inputv, 'ffz_emoticons');
-}
-
-
-API.prototype.unregister_room_set = function(room_id, set_id) {
- var exact_id = this.id + '-' + set_id,
- emote_set = this.emote_sets[set_id],
- room = this.ffz.rooms && this.ffz.rooms[room_id];
-
- if ( ! emote_set || ! room )
- return;
-
- var ind = room.ext_sets ? room.ext_sets.indexOf(exact_id) : -1;
- if ( ind !== -1 )
- room.ext_sets.splice(ind,1);
-
- ind = emote_set.users.indexOf(room_id);
- if ( ind !== -1 )
- emote_set.users.splice(ind,1);
-
- // Update tab completion.
- if ( this.ffz._inputv )
- Ember.propertyDidChange(this.ffz._inputv, 'ffz_emoticons');
-}
-
-
-// -----------------------
-// Badge APIs
-// -----------------------
-
-API.prototype.add_badge = function(badge_id, badge) {
- var exact_id = this.name_key + '-' + badge_id;
-
- badge.id = badge_id;
- badge.source_ext = this.id,
- badge.real_id = exact_id;
-
- if ( badge.replaces && badge.replaces !== true && ! badge.replaces_type ) {
- badge.replaces_type = badge.replaces;
- badge.replaces = true;
- }
-
- if ( ! badge.color )
- badge.color = "transparent";
-
- this.badges[badge_id] = badge;
-
- if ( this.ffz.badges )
- this.ffz.badges[exact_id] = badge;
-
- if ( this.ffz._badge_style )
- utils.update_css(this.ffz._badge_style, exact_id, utils.badge_css(badge));
-}
-
-
-API.prototype.remove_badge = function(badge_id) {
- var exact_id = this.name_key + '-' + badge_id;
- this.badges[badge_id] = undefined;
-
- if ( this.ffz.badges )
- this.ffz.badges[exact_id] = undefined;
-
- if ( this.ffz._badge_style )
- utils.update_css(this.ffz._badge_style, exact_id);
-}
-
-
-// -----------------------
-// User Modifications
-// -----------------------
-
-API.prototype.user_add_badge = function(username, slot, badge_id) {
- var user = this.users[username] = this.users[username] || {},
- ffz_user = this.ffz.users[username] = this.ffz.users[username] || {},
-
- badges = user.badges = user.badges || {},
- ffz_badges = ffz_user.badges = ffz_user.badges || {},
-
- badge = typeof badge_id !== "object" ? {id: badge_id} : badge_id;
-
- badge.real_id = this.name_key + '-' + badge.id;
- badges[slot] = ffz_badges[slot] = badge;
-}
-
-
-API.prototype.user_remove_badge = function(username, slot) {
- var user = this.users[username] = this.users[username] || {},
- ffz_user = this.ffz.users[username] = this.ffz.users[username] || {},
-
- badges = user.badges = user.badges || {},
- ffz_badges = ffz_user.badges = ffz_user.badges || {};
-
- badges[slot] = ffz_badges[slot] = null;
-}
-
-
-API.prototype.room_add_user_badge = function(room_name, username, slot, badge_id) {
- var ffz_room_users = this.ffz.rooms[room_name] && this.ffz.rooms[room_name].users;
- if ( ! ffz_room_users )
- return;
-
- var ffz_user = ffz_room_users[username] = ffz_room_users[username] || {badges: {}, sets: []},
- ffz_badges = ffz_user && ffz_user.badges,
-
- badge = typeof badge_id !== "object" ? {id: badge_id} : badge_id;
-
- badge.real_id = this.name_key + '-' + badge.id;
- ffz_badges[slot] = badge;
-}
-
-
-API.prototype.room_remove_user_badge = function(room_name, username, slot) {
- var ffz_room_users = this.ffz.rooms[room_name] && this.ffz.rooms[room_name].users,
- ffz_user = ffz_room_users && ffz_room_users[username],
- ffz_badges = ffz_user && ffz_user.badges;
-
- if ( ffz_badges )
- ffz_badges[slot] = null;
-}
-
-
-API.prototype.user_add_set = function(username, set_id) {
- var user = this.users[username] = this.users[username] || {},
- ffz_user = this.ffz.users[username] = this.ffz.users[username] || {},
-
- emote_sets = user.sets = user.sets || [],
- ffz_sets = ffz_user.sets = ffz_user.sets || [],
-
- exact_id = this.id + '-' + set_id;
-
- if ( emote_sets.indexOf(set_id) === -1 )
- emote_sets.push(set_id);
-
- if ( ffz_sets.indexOf(exact_id) === -1 )
- ffz_sets.push(exact_id);
-
- // Update tab completion.
- var user = this.ffz.get_user();
- if ( this.ffz._inputv && user && user.login === username )
- Ember.propertyDidChange(this.ffz._inputv, 'ffz_emoticons');
-}
-
-
-API.prototype.user_remove_set = function(username, set_id) {
- var user = this.users[username],
- ffz_user = this.ffz.users[username],
-
- emote_sets = user && user.sets,
- ffz_sets = ffz_user && ffz_user.sets,
-
- exact_id = this.id + '-' + set_id;
-
- var ind = emote_sets ? emote_sets.indexOf(set_id) : -1;
- if ( ind !== -1 )
- emote_sets.splice(ind, 1);
-
- ind = ffz_sets ? ffz_sets.indexOf(exact_id) : -1;
- if ( ind !== -1 )
- ffz_sets.splice(ind, 1);
-
- // Update tab completion.
- var user = this.ffz.get_user();
- if ( this.ffz._inputv && user && user.login === username )
- Ember.propertyDidChange(this.ffz._inputv, 'ffz_emoticons');
-}
-
-
-API.prototype.retokenize_messages = function(room, user, max_age, update_badges) {
- var rooms = room ? [room] : Object.keys(this.ffz.rooms),
- ffz_rooms = this.ffz && this.ffz.rooms || {};
- for(var i=0; i < rooms.length; i++) {
- var room_id = rooms[i],
- room = ffz_rooms[room_id];
- room && room.room && room.room.ffzRetokenizeUser(user, max_age, update_badges);
- }
-}
-
-
-// -----------------------
-// Chat Callback
-// -----------------------
-
-API.prototype.register_chat_filter = function(filter) {
- this.on('room-message', filter);
-}
-
-API.prototype.unregister_chat_filter = function(filter) {
- this.off('room-message', filter);
-}
-
-// -----------------------
-// Channel Callbacks
-// -----------------------
-
-API.prototype.iterate_chat_views = function(func) {
- if ( func === undefined )
- func = this.trigger.bind(this, 'chat-view-init');
-
- if ( this.ffz._chatv ) {
- var view = this.ffz._chatv;
- func(view.get('element'), view);
- }
-}
-
-API.prototype.iterate_rooms = function(func) {
- if ( func === undefined )
- func = this.trigger.bind(this, 'room-add');
-
- for(var room_id in this.ffz.rooms)
- func(room_id);
-}
-
-API.prototype.register_on_room_callback = function(callback, dont_iterate) {
- var cb = this.register_room_set.bind(this, room_id),
- thing = function(room_id) {
- return callback(room_id, cb);
- };
-
- thing.original_func = callback;
- this.on('room-add', thing);
-
- if ( ! dont_iterate )
- this.iterate_rooms(thing);
-}
-
-API.prototype.unregister_on_room_callback = function(callback) {
- var e = this._events['room-add'] || [];
- for(var i=e.length; i--;) {
- var cb = e[i];
- if ( cb && cb.original_func === callback )
- e.splice(i, 1);
- }
-}
\ No newline at end of file
diff --git a/src/ext/betterttv.js b/src/ext/betterttv.js
deleted file mode 100644
index 334749c6..00000000
--- a/src/ext/betterttv.js
+++ /dev/null
@@ -1,539 +0,0 @@
-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 = this.has_bttv_6 = this.has_bttv_7 = false;
- if ( window.BetterTTV && BetterTTV.version && BetterTTV.version.indexOf('7.') === 0 )
- return this.setup_bttv_7(delay||0);
-
- 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_7 = function(delay) {
- this.log("BetterTTV v7 was detected after " + delay + "ms. Hooking.");
- this.has_bttv = 7;
- this.has_bttv_6 = false;
- this.has_bttv_7 = true;
-
- var settings = BetterTTV.settings,
- cl = document.body.classList;
-
- // Disable FFZ Dark if it's enabled.
- cl.remove("ffz-dark");
- if ( this._dark_style ) {
- this._dark_style.parentElement.removeChild(this._dark_style);
- this._dark_style = undefined;
- }
-
- // Disable other styling.
- 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', '');
- }
-
- 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');
-
- //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");
- cl.remove('ffz-minimal-channel-bar');
- cl.remove('ffz-channel-bar-bottom');
- cl.remove('ffz-channel-title-top');
- cl.remove('ffz-sidebar-minimize');
- cl.remove('ffz-alias-italics');
-
- // Update the layout service.
- var Layout = utils.ember_lookup('service:layout');
- if ( Layout ) {
- Layout.set('ffzMinimizeNavigation', false);
- Layout.set('rawPortraitMode', 0);
- }
-
- // Remove Following Count
- if ( this.settings.following_count ) {
- this._schedule_following_count();
- this._draw_following_count();
- this._draw_following_channels();
- }
-
-
- /* Update the chat input to not use FFZ's input handling.
- if ( this._inputv ) {
- var t = this._inputv.$("textarea");
- t.off("keyup");
- t.off("keydown");
- t.off("keypress");
- t.on("keyup", this._inputv._onKeyUp.bind(this._inputv));
- t.on("keydown", this._inputv._onKeyDown.bind(this._inputv));
- }//*/
-
-
-
- // Hook into BTTV's dark mode.
- cl.add('ffz-bttv');
- cl.toggle('ffz-bttv-dark', settings.get('darkenedMode'));
-
- settings.on('changed.darkenedMode', function(val) {
- cl.toggle('ffz-bttv-dark', val);
- });
-
- this.update_ui_link();
- this._roomv && this._roomv.ffzUpdateRecent();
- this.api_trigger('bttv-initialized', 7);
-}
-
-
-FFZ.prototype.setup_bttv = function(delay) {
- this.log("BetterTTV was detected after " + delay + "ms. Hooking.");
- this.has_bttv = true;
- this.has_bttv_6 = true;
- this.has_bttv_7 = false;
-
- // 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
- this._roomv.ffzDisableFreeze();
- this._roomv.ffzRemoveKeyHook();
-
- // And hide the status
- if ( this.settings.room_status )
- this._roomv.ffzUpdateStatus();
- }
-
- this.disconnect_extra_chat();
-
- // Disable style blocks.
- 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");
- cl.remove('ffz-minimal-channel-bar');
- cl.remove('ffz-channel-bar-bottom');
- cl.remove('ffz-channel-title-top');
- cl.remove('ffz-sidebar-minimize');
- cl.remove('ffz-alias-italics');
-
- // Update the layout service.
- var Layout = utils.ember_lookup('service:layout');
- if ( Layout ) {
- Layout.set('ffzMinimizeNavigation', false);
- Layout.set('rawPortraitMode', 0);
- }
-
- // 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.
- data.room = data.room || received_room;
- f.bttv_badges(data);
-
- // API Support
- f.api_trigger('bttv-room-message', data, opts);
-
- // Now, do everything else manually because things are hard-coded.
- return '
+ Okay, still here? Great! You can provide feedback and bug reports by + + opening an issue at our GitHub repository. + + You can also + tweet at us. +
+ ++ When creating a GitHub issue, please check that someone else hasn't + already created one for what you'd like to discuss or report. +
+ ++ This is the initial, beta release of FrankerFaceZ v4.0 with support + for the Twitch website rewrite. + + As you'll notice, this release is not complete. + There are missing features. There are bugs. If you are a moderator, + you will want to just keep opening a Legacy Chat Popout for now. +
+ ++ FrankerFaceZ v4.0 is still under heavy development and there will + be significant changes and improvements in the coming weeks. For + now, here are some of the bigger issues: +
+ +And the biggest features still under development:
+ ++ For a possibly more up-to-date list of what I'm working on, + please consult this Trello board. +
+ +