diff --git a/src/badges.js b/src/badges.js index 8277fb01..09375e8d 100644 --- a/src/badges.js +++ b/src/badges.js @@ -430,38 +430,38 @@ FFZ.prototype._legacy_add_donors = function() { this._legacy_load_donors(); } -FFZ.prototype._legacy_load_bots = function(tries) { +FFZ.prototype._legacy_load_bots = function(callback, tries) { jQuery.ajax(constants.SERVER + "script/bots.txt", {context: this}) .done(function(data) { - this._legacy_parse_badges(data, 0, 2, "Bot (By: {})"); + this._legacy_parse_badges(callback, data, 0, 2, "Bot (By: {})"); }).fail(function(data) { if ( data.status == 404 ) - return; + return typeof callback === "function" && callback(false, 0); tries = (tries || 0) + 1; if ( tries < 10 ) - this._legacy_load_bots(tries); + this._legacy_load_bots(callback, tries); }); } -FFZ.prototype._legacy_load_donors = function(tries) { +FFZ.prototype._legacy_load_donors = function(callback, tries) { jQuery.ajax(constants.SERVER + "script/donors.txt", {context: this}) .done(function(data) { - this._legacy_parse_badges(data, 1, 1); + this._legacy_parse_badges(callback, data, 1, 1); }).fail(function(data) { if ( data.status == 404 ) - return; + return typeof callback === "function" && callback(false, 0); tries = (tries || 0) + 1; if ( tries < 10 ) - return this._legacy_load_donors(tries); + return this._legacy_load_donors(callback, tries); }); } -FFZ.prototype._legacy_parse_badges = function(data, slot, badge_id, title_template) { +FFZ.prototype._legacy_parse_badges = function(callback, data, slot, badge_id, title_template) { var title = this.badges[badge_id].title, count = 0; ds = null; @@ -491,4 +491,8 @@ FFZ.prototype._legacy_parse_badges = function(data, slot, badge_id, title_templa } this.log('Added "' + title + '" badge to ' + utils.number_commas(count) + " users."); + if ( callback ) + callback(true, count); + + return count; } \ No newline at end of file diff --git a/src/commands.js b/src/commands.js index a4a90133..ff279b11 100644 --- a/src/commands.js +++ b/src/commands.js @@ -1,4 +1,5 @@ -var FFZ = window.FrankerFaceZ; +var FFZ = window.FrankerFaceZ, + utils = require('./utils'); // ----------------- @@ -15,6 +16,59 @@ FFZ.ffz_commands.log = function(room, args) { }; +// ----------------- +// Data Reload +// ----------------- + +FFZ.ffz_commands.reload = function(room, args) { + var f = this, + promises = []; + + // Badge Information + promises.push(new Promise(function(done, fail) { + f._legacy_load_bots(function(success, count) { + done(count || 0); + }); + })); + + promises.push(new Promise(function(done, fail) { + f._legacy_load_donors(function(success, count) { + done(count || 0); + }); + })); + + + // Emote Sets + for(var set_id in this.emote_sets) { + var es = this.emote_sets[set_id]; + if ( es.hasOwnProperty('source_ext') ) + continue; + + promises.push(new Promise(function(done, fail) { + f.load_set(set_id, done); + })); + } + + + // Do it! + Promise.all(promises).then(function(results) { + var success = 0, + bots = results[0], + donors = results[1], + total = results.length - 2; + + if ( results.length > 2 ) { + for(var i=2; i < results.length; i++) { + if ( results[i] ) + success++; + } + } + + f.room_message(room, "Loaded " + utils.number_commas(bots) + " new bot badge" + utils.pluralize(bots) + " and " + utils.number_commas(donors) + " new donor badge" + utils.pluralize(donors) + ". Successfully reloaded " + utils.number_commas(success) + " of " + utils.number_commas(total) + " emoticon set" + utils.pluralize(total) + "."); + }) +} + + // ----------------- // Mass Moderation // ----------------- @@ -75,7 +129,7 @@ FFZ.ffz_commands.massmod.help = "Usage: /ffz massmod \nBroadcas /*FFZ.ffz_commands.massunban = function(room, args) { args = args.join(" ").trim(); - - - + + + }*/ \ No newline at end of file diff --git a/src/constants.js b/src/constants.js index 75e56249..836d1bdd 100644 --- a/src/constants.js +++ b/src/constants.js @@ -33,6 +33,8 @@ module.exports = { "Gr(a|e)yFace": "GrayFace" }, + EMOTE_MIRROR_BASE: SERVER + "twitch-emote-mirror/", + EMOTE_REPLACEMENT_BASE: SERVER + "script/replacements/", EMOTE_REPLACEMENTS: { 15: "15-JKanStyle.png", diff --git a/src/ember/channel.js b/src/ember/channel.js index ea154da4..a113db0a 100644 --- a/src/ember/channel.js +++ b/src/ember/channel.js @@ -456,10 +456,9 @@ FFZ.prototype._modify_cindex = function(view) { el = stat_el && stat_el.querySelector('span'), player_cont = f.players && f.players[channel_id], - player = player_cont && player_cont.player, + player = player_cont && player_cont.ffz_player, stats = player && player.stats; - if ( ! container || ! f.settings.player_stats || ! stats || stats.hlsLatencyBroadcaster === 'NaN' || stats.hlsLatencyBroadcaster === NaN ) { if ( stat_el ) stat_el.parentElement.removeChild(stat_el); @@ -509,7 +508,7 @@ FFZ.prototype._modify_cindex = function(view) { el = stat_el && stat_el.querySelector('span'), player_cont = f.players && f.players[hosted_id], - player = player_cont && player_cont.player, + player = player_cont && player_cont.ffz_player, stats = player && player.stats; diff --git a/src/ember/chatview.js b/src/ember/chatview.js index f6d6fb02..06ce3945 100644 --- a/src/ember/chatview.js +++ b/src/ember/chatview.js @@ -299,7 +299,7 @@ FFZ.prototype.setup_chatview = function() { this.blurRoom(); // Don't destroy it if it's the user's room. - if ( room && user && user.login === room_id ) + if ( room && user && user.login !== room_id ) room.destroy(); } diff --git a/src/ember/line.js b/src/ember/line.js index 81d51e37..40827a89 100644 --- a/src/ember/line.js +++ b/src/ember/line.js @@ -667,10 +667,21 @@ FFZ.prototype._modify_line = function(component) { else { eid = e.target.getAttribute("data-ffz-emote"); var es = e.target.getAttribute("data-ffz-set"), - set = es && f.emote_sets[es]; + set = es && f.emote_sets[es], + url; - if ( ! set || ! set.hasOwnProperty('source_ext') ) - window.open("https://www.frankerfacez.com/emoticons/" + eid); + if ( ! set ) + return; + + if ( set.hasOwnProperty('source_ext') ) { + var api = f._apis[set.source_ext]; + if ( api && api.emote_url_generator ) + url = api.emote_url_generator(set.source_id, eid); + } else + url = "https://www.frankerfacez.com/emoticons/" + eid; + + if ( url ) + window.open(url); } } diff --git a/src/ember/player.js b/src/ember/player.js index 46b6df6f..8b714677 100644 --- a/src/ember/player.js +++ b/src/ember/player.js @@ -84,7 +84,9 @@ FFZ.prototype.setup_player = function() { try { this._modify_player(view); view.ffzInit(); - if ( view.get('player') ) + + var tp2 = window.require("web-client/components/twitch-player2"); + if ( tp2 && tp2.getPlayer && tp2.getPlayer() ) view.ffzPostPlayer(); } catch(err) { @@ -150,9 +152,18 @@ FFZ.prototype._modify_player = function(player) { }, ffzPostPlayer: function() { - var player = this.get('player'); - if ( ! player ) - return; + var player = this.get('ffz_player') || this.get('player'); + if ( ! player ) { + var tp2 = window.require("web-client/components/twitch-player2"); + if ( ! tp2 || ! tp2.getPlayer ) + return; + + player = tp2.getPlayer(); + if ( ! player ) + return; + } + + this.set('ffz_player', player); // Only set up the stats hooks if we need stats. if ( ! player.getVideo() ) @@ -163,7 +174,7 @@ FFZ.prototype._modify_player = function(player) { if ( this.get('ffzStatsInitialized') ) return; - var player = this.get('player'); + var player = this.get('ffz_player'); if ( ! player ) return; @@ -210,7 +221,7 @@ FFZ.prototype._modify_player = function(player) { }, ffzSetQuality: function(q) { - var player = this.get('player'); + var player = this.get('ffz_player'); if ( ! player ) return; @@ -224,7 +235,7 @@ FFZ.prototype._modify_player = function(player) { }, ffzGetQualities: function() { - var player = this.get('player'); + var player = this.get('ffz_player'); if ( ! player ) return []; return player.getQualities(); diff --git a/src/emoticons.js b/src/emoticons.js index e82e126a..1e59d883 100644 --- a/src/emoticons.js +++ b/src/emoticons.js @@ -400,9 +400,9 @@ FFZ.prototype._load_set_json = function(set_id, callback, data) { emote.srcSet += ", " + emote.urls[4] + " 4x"; if ( emote.name[emote.name.length-1] === "!" ) - emote.regex = new RegExp("(^|\\W|\\b)(" + RegExp.escape(emote.name) + ")(?=\\W|$)", "g"); + emote.regex = new RegExp("(^|\\W|\\b)(" + utils.escape_regex(emote.name) + ")(?=\\W|$)", "g"); else - emote.regex = new RegExp("(^|\\W|\\b)(" + RegExp.escape(emote.name) + ")\\b", "g"); + emote.regex = new RegExp("(^|\\W|\\b)(" + utils.escape_regex(emote.name) + ")\\b", "g"); output_css += build_css(emote); data.count++; diff --git a/src/ext/api.js b/src/ext/api.js index 75e94850..5c720003 100644 --- a/src/ext/api.js +++ b/src/ext/api.js @@ -159,7 +159,7 @@ API.prototype._load_set = function(real_id, set_id, data) { else if ( typeof emote.name !== "string" ) new_emote.regex = emote.name; else - new_emote.regex = new RegExp("(^|\\W|\\b)(" + RegExp.escape(emote.name) + ")(?=\\W|$)", "g"); + new_emote.regex = new RegExp("(^|\\W|\\b)(" + utils.escape_regex(emote.name) + ")(?=\\W|$)", "g"); output_css += build_css(new_emote); set.count++; @@ -179,19 +179,87 @@ API.prototype._load_set = function(real_id, set_id, data) { } +// ------------------------- +// Loading / Unloading Sets +// ------------------------- + +API.prototype.load_set = function(id, emote_set) { + var exact_id = this.id + '-' + id; + + emote_set.title = emote_set.title || "Global Emoticons"; + emote_set._type = emote_set._type || 0; + + emote_set = this._load_set(exact_id, id, emote_set); + this.log("Loaded Emoticon Set #" + id + ": " + emote_set.title + " (" + emote_set.count + " emotes)", emote_set); + return emote_set; +} + + +API.prototype.unload_set = function(id) { + var exact_id = this.id + '-' + id, + emote_set = this.emote_sets[exact_id]; + + if ( ! emote_set ) + return; + + // First, let's unregister it as a global. + this.unregister_global_set(id); + + + // Now, remove the set data. + utils.update_css(this.ffz._emote_style, exact_id, null); + + this.emote_sets[exact_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; + + ind = room.ext_sets.indexOf(exact_id); + if ( ind !== -1 ) + room.ext_sets.splice(ind,1); + } + + emote_set.users = []; + } + + + return set; +} + + +API.prototype.get_set = function(id) { + var exact_id = this.id + '-' + id; + return this.emote_sets[exact_id]; +} + + // --------------------- // Global Emote Sets // --------------------- -API.prototype.register_global_set = function(id, set) { +API.prototype.register_global_set = function(id, emote_set) { var exact_id = this.id + '-' + id; - set.title = set.title || "Global Emoticons"; - set._type = 0; - set = this._load_set(exact_id, id, set); + if ( emote_set ) { + // If a set was provided, load it. + emote_set = this.load_set(id, emote_set); + } else + emote_set = this.emote_sets[exact_id]; - this.log("Loaded Emoticon Set #" + id + ": " + set.title + " (" + set.count + " emotes)", set); + if ( ! emote_set ) + throw new Error("Invalid set ID"); + + // It's a valid set if we get here, so make it global. if ( this.global_sets.indexOf(exact_id) === -1 ) this.global_sets.push(exact_id); @@ -208,18 +276,12 @@ API.prototype.register_global_set = function(id, set) { API.prototype.unregister_global_set = function(id) { var exact_id = this.id + '-' + id, - set = this.emote_sets[exact_id]; + emote_set = this.emote_sets[exact_id]; - if ( ! set ) + if ( ! emote_set ) return; - utils.update_css(this.ffz._emote_style, exact_id, null); - - this.emote_sets[exact_id] = undefined; - - if ( this.ffz.emote_sets ) - this.ffz.emote_sets[exact_id] = undefined; - + // Remove the set from global sets. var ind = this.global_sets.indexOf(exact_id); if ( ind !== -1 ) this.global_sets.splice(ind,1); @@ -235,8 +297,6 @@ API.prototype.unregister_global_set = function(id) { ind = this.ffz.default_sets ? this.ffz.default_sets.indexOf(exact_id) : -1; if ( ind !== -1 ) this.ffz.default_sets.splice(ind,1); - - this.log("Unloaded Emoticon Set #" + id + ": " + set.title, set);; }; @@ -244,21 +304,55 @@ API.prototype.unregister_global_set = function(id) { // Per-Channel Emote Sets // ----------------------- +API.prototype.register_room_set = function(room_id, id, emote_set) { + var exact_id = this.id + '-' + 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(id, emote_set); + } else + emote_set = this.emote_sets[exact_id]; + + if ( ! emote_set ) + throw new Error("Invalid set ID"); + + // Register it on the room. + room.ext_sets.push(exact_id); + emote_set.users.push(room_id); +} + + +API.prototype.unregister_room_set = function(room_id, id) { + var exact_id = this.id + '-' + id, + emote_set = this.emote_sets[exact_id], + room = this.ffz.rooms && this.ffz.rooms[room_id]; + + if ( ! emote_set || ! room ) + return; + + var ind = room.ext_sets.indexOf(exact_id); + 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); +} + + +// ----------------------- +// Channel Callbacks +// ----------------------- + API.prototype._room_callbacks = function(room_id, room, specific_func) { - var api = this; - - var callback = function(id, set) { - var exact_id = api.id + '-' + id; - - set.title = set.title || "Channel: " + room_id; - set._type = 1; - - set = api._load_set(exact_id, id, set); - api.log("Loaded Emoticon Set #" + id + ": " + set.title + " (" + set.count + " emotes)", set); - - room.ext_sets.push(exact_id); - set.users.push(room_id); - }; + var callback = this.register_room_set.bind(this, room_id); if ( specific_func ) { try { @@ -280,11 +374,11 @@ API.prototype._room_callbacks = function(room_id, room, specific_func) { } -API.prototype.register_on_room_callback = function(callback) { +API.prototype.register_on_room_callback = function(callback, dont_iterate) { this.on_room_callbacks.push(callback); // Call this for all current rooms. - if ( this.ffz.rooms ) { + if ( ! dont_iterate && this.ffz.rooms ) { for(var room_id in this.ffz.rooms) this._room_callbacks(room_id, this.ffz.rooms[room_id], callback); } diff --git a/src/main.js b/src/main.js index 6c655f70..df2f440f 100644 --- a/src/main.js +++ b/src/main.js @@ -22,7 +22,7 @@ FFZ.get = function() { return FFZ.instance; } // Version var VER = FFZ.version_info = { - major: 3, minor: 5, revision: 36, + major: 3, minor: 5, revision: 40, toString: function() { return [VER.major, VER.minor, VER.revision].join(".") + (VER.extra || ""); } diff --git a/src/settings.js b/src/settings.js index facd1ec5..cae0c154 100644 --- a/src/settings.js +++ b/src/settings.js @@ -828,10 +828,14 @@ FFZ.prototype._setting_load = function(key, default_value) { val = info.process_value.bind(this)(val); this.settings[key] = val; + return val; } FFZ.prototype._setting_get = function(key) { + if ( ! this.settings.hasOwnProperty(key) && FFZ.settings_info[key] ) + this._setting_load(key); + return this.settings[key]; } diff --git a/src/tokenize.js b/src/tokenize.js index 618b43c3..12871234 100644 --- a/src/tokenize.js +++ b/src/tokenize.js @@ -240,6 +240,24 @@ FFZ.src_to_id = function(src) { }; +FFZ._emote_mirror_swap = function(img) { + var src, attempts = parseInt(img.getAttribute('data-alt-attempts')) || 0; + if ( attempts > 3 ) + return; + + img.setAttribute('data-alt-attempts', attempts + 1); + var id = img.getAttribute('data-emote'); + + if ( img.src.substr(0, TWITCH_BASE.length) === TWITCH_BASE ) { + img.src = constants.EMOTE_MIRROR_BASE + id + ".png"; + img.srcset = ""; + } else { + img.src = TWITCH_BASE + id + "/1.0"; + img.srcset = build_srcset(id); + } +} + + // --------------------- // Settings // --------------------- @@ -582,7 +600,9 @@ FFZ.prototype.render_tokens = function(tokens, render_links) { } } - extra = ' data-emote="' + id + '"'; + var mirror_url = utils.quote_attr(constants.EMOTE_MIRROR_BASE + id + '.png'); + + extra = ' data-emote="' + id + '" onerror="FrankerFaceZ._emote_mirror_swap(this)"'; if ( ! constants.EMOTE_REPLACEMENTS[id] ) srcset = build_srcset(id); diff --git a/src/ui/menu.js b/src/ui/menu.js index 7e135abb..07093642 100644 --- a/src/ui/menu.js +++ b/src/ui/menu.js @@ -616,9 +616,17 @@ FFZ.prototype._emotes_for_sets = function(parent, view, sets, header, image, sub s.addEventListener('click', function(id, code, e) { e.preventDefault(); - if ( (e.shiftKey || e.shiftLeft) && f.settings.clickable_emoticons && ! set.hasOwnProperty('source_ext') ) - window.open("https://www.frankerfacez.com/emoticons/" + id); - else + if ( (e.shiftKey || e.shiftLeft) && f.settings.clickable_emoticons ) { + var url; + if ( set.hasOwnProperty('source_ext') ) { + var api = f._apis[set.source_ext]; + if ( api && api.emote_url_generator ) + url = api.emote_url_generator(set.source_id, id); + } else + url = "https://www.frankerfacez.com/emoticons/" + id; + if ( url ) + window.open(url); + } else this._add_emote(view, code); }.bind(this, emote.id, emote.name)); diff --git a/src/ui/my_emotes.js b/src/ui/my_emotes.js index 2379bbec..06d23479 100644 --- a/src/ui/my_emotes.js +++ b/src/ui/my_emotes.js @@ -331,9 +331,18 @@ FFZ.menu_pages.myemotes = { em.title = this._emote_tooltip(emote); em.addEventListener("click", function(id, code, e) { e.preventDefault(); - if ( (e.shiftKey || e.shiftLeft) && f.settings.clickable_emoticons && ! set.hasOwnProperty('source_ext') ) - window.open("https://www.frankerfacez.com/emoticons/" + id); - else + if ( (e.shiftKey || e.shiftLeft) && f.settings.clickable_emoticons ) { + var url; + if ( set.hasOwnProperty('source_ext') ) { + var api = f._apis[set.source_ext]; + if ( api && api.emote_url_generator ) + url = api.emote_url_generator(set.source_id, id); + } else + url = "https://www.frankerfacez.com/emoticons/" + id; + + if ( url ) + window.open(url); + } else this._add_emote(view, code); }.bind(this, emote.id, emote.name)); menu.appendChild(em); @@ -387,14 +396,14 @@ FFZ.menu_pages.myemotes = { var an = a[0], bn = b[0]; if ( an === "turbo" || an === "--turbo-faces--" ) an = "zza|" + an; - else if ( an === "global" || an === "global emoticons" || an === "--global--" ) + else if ( an === "global" || (an && an.substr(0,16) === "global emoticons") || an === "--global--" ) an = "zzy|" + an; else if ( an === "emoji" ) an = "zzz|" + an; if ( bn === "turbo" || bn === "--turbo-faces--" ) bn = "zza|" + bn; - else if ( bn === "global" || bn === "global emoticons" || bn === "--global--" ) + else if ( bn === "global" || (bn && bn.substr(0,16) === "global emoticons") || bn === "--global--" ) bn = "zzy|" + bn; else if ( bn === "emoji" ) bn = "zzz|" + bn; diff --git a/src/utils.js b/src/utils.js index 512e008f..f9794ce9 100644 --- a/src/utils.js +++ b/src/utils.js @@ -9,6 +9,10 @@ var sanitize_el = document.createElement('span'), return sanitize_el.innerHTML; }, + escape_regex = RegExp.escape || function(str) { + return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + }, + R_QUOTE = /"/g, R_SQUOTE = /'/g, R_AMP = /&/g, @@ -311,5 +315,7 @@ module.exports = { return "99+"; return "" + count; - } + }, + + escape_regex: escape_regex } \ No newline at end of file