From d934508b04a79b495b6d9a136b536a279a27ac9c Mon Sep 17 00:00:00 2001 From: SirStendec Date: Sat, 24 Oct 2015 22:44:00 -0400 Subject: [PATCH 1/3] 3.5.31 to 3.5.36. Added Portrait Mode. Fixed missing Op button on mod cards. Changed Feature Friday code to facilitate events without an associated channel, like Halloween. Fixed Stream Latency breaking on a broken player function. Added a simple JS API to let other extensions add emoticons to FFZ. --- dark.css | 6 + src/constants.js | 1 + src/ember/directory.js | 3 - src/ember/layout.js | 164 +++++++++++++++++-- src/ember/line.js | 6 +- src/ember/moderation-card.js | 8 +- src/ember/player.js | 12 +- src/ember/room.js | 10 +- src/ember/router.js | 27 ++-- src/emoticons.js | 52 ++++-- src/ext/api.js | 298 +++++++++++++++++++++++++++++++++++ src/ext/betterttv.js | 1 + src/ext/emote_menu.js | 2 +- src/featurefriday.js | 13 +- src/main.js | 8 +- src/settings.js | 50 +++--- src/socket.js | 3 + src/tokenize.js | 2 +- src/ui/menu.js | 20 ++- src/ui/my_emotes.js | 18 ++- style.css | 8 +- 21 files changed, 616 insertions(+), 96 deletions(-) create mode 100644 src/ext/api.js diff --git a/dark.css b/dark.css index ecaa814b..de420715 100644 --- a/dark.css +++ b/dark.css @@ -117,6 +117,12 @@ border-right:1px solid rgb(0,0,0)!important; } +.ffz-dark.ffz-portrait div#left_col .column, +.ffz-dark.ffz-portrait div#main_col { + border-right: none !important; + border-bottom: 1px solid rgb(0,0,0) !important; +} + /* stream title */ .ffz-dark span.real_title{ diff --git a/src/constants.js b/src/constants.js index dad381a7..75e56249 100644 --- a/src/constants.js +++ b/src/constants.js @@ -25,6 +25,7 @@ module.exports = { "\\:-?[\\\\/]": ":-/", "\\;-?\\)": ";-)", "R-?\\)": "R-)", + "[oO](_|\\.)[oO]": "O.o", "[o|O](_|\\.)[o|O]": "O.o", "\\:-?D": ":-D", "\\:-?(o|O)": ":-O", diff --git a/src/ember/directory.js b/src/ember/directory.js index 31c218e2..843a8ae7 100644 --- a/src/ember/directory.js +++ b/src/ember/directory.js @@ -47,9 +47,6 @@ FFZ.prototype._modify_directory_video = function(dir) { dir.reopen({ didInsertElement: function() { this._super(); - - f.log("New Video View", this); - window.v = this; } }); diff --git a/src/ember/layout.js b/src/ember/layout.js index 0d4de51b..8fb0c58f 100644 --- a/src/ember/layout.js +++ b/src/ember/layout.js @@ -5,6 +5,48 @@ var FFZ = window.FrankerFaceZ; // Settings // -------------------- +FFZ.settings_info.portrait_mode = { + type: "select", + options: { + 0: "Disabled", + 1: "Automatic (Use Window Aspect Ratio)", + 2: "Always On", + 3: "Automatic (Video Below)", + 4: "Always On (Video Below)" + }, + + value: 0, + + process_value: function(val) { + if ( val === false ) + return 0; + if ( val === true ) + return 1; + if ( typeof val === "string" ) + return parseInt(val) || 0; + return val; + }, + + category: "Appearance", + no_mobile: true, + no_bttv: true, + + name: "Portrait Mode (Chat Below Video)", + help: "Display the right sidebar beneath (or above) the video player for viewing in portrait orientations.", + + on_update: function(val) { + if ( this.has_bttv ) + return; + + var Layout = window.App && App.__container__.lookup('controller:layout'); + if ( ! Layout ) + return; + + Layout.set('rawPortraitMode', val); + this._fix_menu_position(); + } +} + FFZ.settings_info.swap_sidebars = { type: "boolean", value: false, @@ -94,6 +136,7 @@ FFZ.prototype.setup_layout = function() { return; document.body.classList.toggle("ffz-sidebar-swap", this.settings.swap_sidebars); + document.body.classList.toggle("ffz-portrait", this.settings.portrait_mode); this.log("Creating layout style element."); var s = this._layout_style = document.createElement('style'); @@ -109,20 +152,47 @@ FFZ.prototype.setup_layout = function() { Layout.reopen({ rightColumnWidth: 340, + rawPortraitMode: 0, + + portraitVideoBelow: false, + + 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() { - return this.get("windowWidth") < (1090 - this.get('rightColumnWidth')) - }.property("windowWidth", "rightColumnWidth"), + if ( ! f.has_bttv && this.get('portraitMode') ) { + var size = this.get('playerSize'), + height = size[1]; + + // Make sure we have at least a bit of room for the chat. + return this.get("windowHeight") < (height + 120 + 60 + 100); + + } else + return this.get("windowWidth") < (1090 - this.get('rightColumnWidth')) + + }.property("windowWidth", "rightColumnWidth", "playerSize", "windowHeight"), contentWidth: function() { var left_width = this.get("isLeftColumnClosed") ? 50 : 240, - right_width = this.get("isRightColumnClosed") ? 0 : this.get("rightColumnWidth"); + right_width = ! f.has_bttv && this.get('portraitMode') ? 0 : this.get("isRightColumnClosed") ? 0 : this.get("rightColumnWidth"); return this.get("windowWidth") - left_width - right_width - 60; - }.property("windowWidth", "isRightColumnClosed", "isLeftColumnClosed", "rightColumnWidth"), + }.property("windowWidth", "portraitMode", "isRightColumnClosed", "isLeftColumnClosed", "rightColumnWidth"), - playerStyle: function() { + playerSize: function() { var h = this.get('windowHeight'), c = this.get('PLAYER_CONTROLS_HEIGHT'), r = this.get('contentWidth'), @@ -135,8 +205,17 @@ FFZ.prototype.setup_layout = function() { o = Math.floor(Math.min(i, d)), s = Math.floor(Math.min(i, c)); - return ""; - }.property("contentWidth", "windowHeight", "PLAYER_CONTROLS_HEIGHT"), + return [l, o, s]; + }.property("contentWidth", "windowHeight", "portraitMode", "PLAYER_CONTROLS_HEIGHT"), + + playerStyle: function() { + var size = this.get('playerSize'), + width = size[0], + height = size[1], + host_height = size[2]; + + return ""; + }.property("playerSize"), /*ffzUpdateWidth: _.throttle(function() { var rc = document.querySelector('#right_close'); @@ -161,11 +240,70 @@ FFZ.prototype.setup_layout = function() { }, 200),*/ ffzUpdateCss: function() { - var width = this.get('rightColumnWidth'); + // TODO: Fix this mess of duplicate code. + var out = ''; - f._layout_style.innerHTML = '#main_col.expandRight #right_close { left: none !important; } #right_col { width: ' + width + 'px; } body:not(.ffz-sidebar-swap) #main_col:not(.expandRight) { margin-right: ' + width + 'px; } body.ffz-sidebar-swap #main_col:not(.expandRight) { margin-left: ' + width + 'px; }'; + if ( ! f.has_bttv ) { + if ( this.get('portraitMode') ) { + var size = this.get('playerSize'), + height = size[1], + top = height + 120 + 60; - }.observes("rightColumnWidth"), + if ( this.get('portraitVideoBelow') ) { + var wh = this.get("windowHeight"), + mch = wh - top; + + out = (this.get('isRightColumnClosed') ? '' : 'body[data-current-path^="user."] #left_col, ') + + 'body[data-current-path^="user."]:not(.ffz-sidebar-swap) #main_col:not(.expandRight) { margin-right: 0 !important; top: ' + mch + 'px; height: ' + top + 'px; }' + + 'body[data-current-path^="user."].ffz-sidebar-swap #main_col:not(.expandRight) { margin-left: 0 !important; top: ' + mch + 'px; height: ' + top + 'px; }' + + 'body[data-current-path^="user."] #right_col { width: 100%; height: ' + mch + 'px; left: 0; }'; + } else + out = (this.get('isRightColumnClosed') ? '' : 'body[data-current-path^="user."] #left_col, ') + + 'body[data-current-path^="user."]:not(.ffz-sidebar-swap) #main_col:not(.expandRight) { margin-right: 0 !important; height: ' + top + 'px; }' + + 'body[data-current-path^="user."].ffz-sidebar-swap #main_col:not(.expandRight) { margin-left: 0 !important; height: ' + top + 'px; }' + + 'body[data-current-path^="user."] #right_col { width: 100%; top: ' + top + 'px; left: 0; }'; + + // Theatre Mode Portrait + if ( true ) { //this.get('theaterPortraitMode') ) { + // Recalculate the player height, not including the title or anything special. + var width = this.get("windowWidth"), + wh = this.get("windowHeight"), + height = (9 * width / 16); + + height = Math.floor(Math.max(wh * 0.1, Math.min(wh - 300, height))); + + if ( this.get('portraitVideoBelow') ) { + var mch = this.get("windowHeight") - height; + + out += (this.get('isRightColumnClosed') ? '' : 'body[data-current-path^="user."] .app-main.theatre #left_col, ') + + 'body[data-current-path^="user."]:not(.ffz-sidebar-swap) .app-main.theatre #main_col:not(.expandRight) { margin-right: 0 !important; top: ' + mch + 'px; height: ' + height + 'px; }' + + 'body[data-current-path^="user."].ffz-sidebar-swap .app-main.theatre #main_col:not(.expandRight) { margin-left: 0 !important; top: ' + mch + 'px; height: ' + height + 'px; }' + + 'body[data-current-path^="user."] .app-main.theatre #right_col { width: 100%; height: ' + mch + 'px; left: 0; }'; + } else + out += 'body[data-current-path^="user."]:not(.ffz-sidebar-swap) .app-main.theatre #main_col:not(.expandRight) { margin-right: 0 !important; height: ' + height + 'px; }' + + 'body[data-current-path^="user."].ffz-sidebar-swap .app-main.theatre #main_col:not(.expandRight) { margin-left: 0 !important; height: ' + height + 'px; }' + + 'body[data-current-path^="user."] .app-main.theatre #right_col { width: 100%; top: ' + height + 'px; left: 0; }'; + } + + } else { + var width = this.get('rightColumnWidth'); + + out = '#main_col.expandRight #right_close { left: none !important; }' + + '#right_col { width: ' + width + 'px; }' + + 'body:not(.ffz-sidebar-swap) #main_col:not(.expandRight) { margin-right: ' + width + 'px; }' + + 'body.ffz-sidebar-swap #main_col:not(.expandRight) { margin-left: ' + width + 'px; }'; + } + + f._layout_style.innerHTML = out; + } + + }.observes("isRightColumnClosed", "playerSize", "rightColumnWidth", "portraitMode", "windowHeight", "windowWidth"), + + ffzUpdatePortraitCSS: function() { + var portrait = this.get("portraitMode"); + document.body.classList.toggle("ffz-portrait", ! f.has_bttv && portrait); + + }.observes("portraitMode"), ffzFixTabs: function() { if ( f.settings.group_tabs && f._chatv && f._chatv._ffz_tabs ) { @@ -173,7 +311,7 @@ FFZ.prototype.setup_layout = function() { f._chatv && f._chatv.$('.chat-room').css('top', f._chatv._ffz_tabs.offsetHeight + "px"); },0); } - }.observes("isRightColumnClosed", "rightColumnWidth") + }.observes("isRightColumnClosed", "rightColumnWidth", "portraitMode", "playerSize") }); /* @@ -191,5 +329,9 @@ FFZ.prototype.setup_layout = function() { // Force the layout to update. Layout.set('rightColumnWidth', this.settings.right_column_width); + Layout.set('rawPortraitMode', this.settings.portrait_mode); + + // Force re-calculation of everything. Ember.propertyDidChange(Layout, 'contentWidth'); + Ember.propertyDidChange(Layout, 'windowHeight'); } \ No newline at end of file diff --git a/src/ember/line.js b/src/ember/line.js index a1955756..81d51e37 100644 --- a/src/ember/line.js +++ b/src/ember/line.js @@ -666,7 +666,11 @@ FFZ.prototype._modify_line = function(component) { window.open("https://twitchemotes.com/emote/" + eid); else { eid = e.target.getAttribute("data-ffz-emote"); - window.open("https://www.frankerfacez.com/emoticons/" + eid); + var es = e.target.getAttribute("data-ffz-set"), + set = es && f.emote_sets[es]; + + if ( ! set || ! set.hasOwnProperty('source_ext') ) + window.open("https://www.frankerfacez.com/emoticons/" + eid); } } diff --git a/src/ember/moderation-card.js b/src/ember/moderation-card.js index 37cf14e9..4100d297 100644 --- a/src/ember/moderation-card.js +++ b/src/ember/moderation-card.js @@ -448,6 +448,10 @@ FFZ.prototype.setup_mod_card = function() { is_mod = controller.get('cardInfo.isModeratorOrHigher'), + chat = window.App && App.__container__.lookup('controller:chat'), + user = f.get_user(), + is_broadcaster = user && chat && chat.get('currentRoom.id') === user.login, + user_id = controller.get('cardInfo.user.id'), alias = f.aliases[user_id]; @@ -633,9 +637,7 @@ FFZ.prototype.setup_mod_card = function() { // More Fixing Other Buttons var op_btn = el.querySelector('button.mod'); if ( op_btn ) { - var is_owner = controller.get('cardInfo.isChannelOwner'), - user = ffz.get_user(); - can_op = is_owner || (user && user.is_admin) || (user && user.is_staff); + var can_op = is_broadcaster || (user && user.is_admin) || (user && user.is_staff); if ( ! can_op ) op_btn.parentElement.removeChild(op_btn); diff --git a/src/ember/player.js b/src/ember/player.js index bfa09250..46b6df6f 100644 --- a/src/ember/player.js +++ b/src/ember/player.js @@ -154,10 +154,6 @@ FFZ.prototype._modify_player = function(player) { if ( ! player ) return; - // Subscribe to the qualities event. - //player.addEventListener('qualitieschange', this.ffzQualitiesUpdated.bind(this)); - //this.ffzQualitiesUpdated(); - // Only set up the stats hooks if we need stats. if ( ! player.getVideo() ) this.ffzInitStats(); @@ -175,7 +171,13 @@ FFZ.prototype._modify_player = function(player) { // Make it so stats can no longer be disabled if we want them. player.ffzSetStatsEnabled = player.setStatsEnabled; - player.ffz_stats = player.getStatsEnabled(); + try { + player.ffz_stats = player.getStatsEnabled(); + } catch(err) { + // Assume stats are off. + f.log("player ffzInitStats: getStatsEnabled still doesn't work: " + err); + player.ffz_stats = false; + } var t = this; diff --git a/src/ember/room.js b/src/ember/room.js index a4b04135..c0184363 100644 --- a/src/ember/room.js +++ b/src/ember/room.js @@ -567,7 +567,7 @@ FFZ.prototype.add_room = function(id, room) { this.log("Adding Room: " + id); // Create a basic data table for this room. - var data = this.rooms[id] = {id: id, room: room, menu_sets: [], sets: [], css: null, needs_history: false}; + var data = this.rooms[id] = {id: id, room: room, sets: [], ext_sets: [], css: null, needs_history: false}; if ( this.follow_sets && this.follow_sets[id] ) { data.extra_sets = this.follow_sets[id]; @@ -602,8 +602,14 @@ FFZ.prototype.add_room = function(id, room) { // Why don't we set the scrollback length, too? room.set('messageBufferSize', this.settings.scrollback_length + ((this._roomv && !this._roomv.get('stuckToBottom') && this._roomv.get('controller.model.id') === id) ? 150 : 0)); - // For now, we use the legacy function to grab the .css file. + // Load the room's data from the API. this.load_room(id); + + // Announce this room to any extension callback functions. + for(var api_id in this._apis) { + var api = this._apis[api_id]; + api._room_callbacks(id, data); + } } diff --git a/src/ember/router.js b/src/ember/router.js index 201179c6..26d96818 100644 --- a/src/ember/router.js +++ b/src/ember/router.js @@ -7,15 +7,22 @@ var FFZ = window.FrankerFaceZ; FFZ.prototype.setup_router = function() { this.log("Hooking the Ember router."); + if ( ! window.App ) + return; - var f = this; - App.__container__.lookup('router:main').reopen({ - ffzTransition: function() { - try { - f.track_page(); - } catch(err) { - f.error("ffzTransition: " + err); - } - }.on('didTransition') - }); + var f = this, + Router = App.__container__.lookup('router:main'); + + if ( Router ) + Router.reopen({ + ffzTransition: function() { + try { + document.body.setAttribute('data-current-path', App.get('currentPath')); + } catch(err) { + f.error("ffzTransition: " + err); + } + }.on('didTransition') + }); + + document.body.setAttribute('data-current-path', App.get('currentPath')); } \ No newline at end of file diff --git a/src/emoticons.js b/src/emoticons.js index d22b3f24..e82e126a 100644 --- a/src/emoticons.js +++ b/src/emoticons.js @@ -5,7 +5,7 @@ var FFZ = window.FrankerFaceZ, utils = require('./utils'), - check_margins = function(margins, height) { + /*check_margins = function(margins, height) { var mlist = margins.split(/ +/); if ( mlist.length != 2 ) return margins; @@ -36,19 +36,17 @@ var FFZ = window.FrankerFaceZ, } return ".ffz-emote-" + emote.id + ' { background-image: url("' + emote.urls[1] + '"); height: ' + emote.height + "px; width: " + emote.width + "px; margin: " + margin + (srcset ? '; ' + srcset : '') + (emote.css ? "; " + emote.css : "") + "}\n"; - }, + },*/ - build_new_css = function(emote) { + build_css = function(emote) { if ( ! emote.margins && ! emote.css ) - return build_legacy_css(emote); + return ""; //build_legacy_css(emote); - return build_legacy_css(emote) + 'img[src="' + emote.urls[1] + '"] { ' + (emote.margins ? "margin: " + emote.margins + ";" : "") + (emote.css || "") + " }\n"; + return /*build_legacy_css(emote) +*/ 'img[src="' + emote.urls[1] + '"] { ' + (emote.margins ? "margin: " + emote.margins + ";" : "") + (emote.css || "") + " }\n"; }, - build_css = build_new_css, - from_code_point = function(cp) { var code = typeof cp === "string" ? parseInt(cp, 16) : cp; if ( code < 0x10000) @@ -94,6 +92,27 @@ FFZ.prototype.setup_emoticons = function() { this.log("Watching Twitch emoticon parser to ensure it loads."); this._twitch_emote_check = setTimeout(this.check_twitch_emotes.bind(this), 10000); + + + if ( this._apis ) { + for(var api_id in this._apis) { + var api = this._apis[api_id]; + for(var es_id in api.emote_sets) + this.emote_sets[es_id] = api.emote_sets[es_id]; + + for(var i=0; i < api.global_sets.length; i++) { + var es_id = api.global_sets[i]; + if ( this.global_sets.indexOf(es_id) === -1 ) + this.global_sets.push(es_id); + } + + for(var i=0; i < api.default_sets.length; i++) { + var es_id = api.default_sets[i]; + if ( this.default_sets.indexOf(es_id) === -1 ) + this.default_sets.push(es_id); + } + } + } } @@ -173,7 +192,7 @@ FFZ.prototype.getEmotes = function(user_id, room_id) { var user = this.users && this.users[user_id], room = this.rooms && this.rooms[room_id]; - return _.union(user && user.sets || [], room && room.set && [room.set] || [], room && room.extra_sets || [], this.default_sets); + return _.union(user && user.sets || [], room && room.set && [room.set] || [], room && room.extra_sets || [], room && room.ext_sets || [], this.default_sets); } @@ -205,9 +224,10 @@ FFZ.prototype._emote_tooltip = function(emote) { var set = this.emote_sets[emote.set_id], owner = emote.owner, - title = set && set.title || "Global"; + title = set && set.title || "Global", + source = set && set.source || "FFZ"; - emote._tooltip = "Emoticon: " + (emote.hidden ? "???" : emote.name) + "\nFFZ " + title + (owner ? "\nBy: " + owner.display_name : ""); + emote._tooltip = "Emoticon: " + (emote.hidden ? "???" : emote.name) + "\n" + source + " " + title + (owner ? "\nBy: " + owner.display_name : ""); return emote._tooltip; } @@ -339,6 +359,12 @@ FFZ.prototype.unload_set = function(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; + } } @@ -364,7 +390,7 @@ FFZ.prototype._load_set_json = function(set_id, callback, data) { for(var i=0; i < ems.length; i++) { var emote = ems[i]; - emote.klass = "ffz-emote-" + emote.id; + //emote.klass = "ffz-emote-" + emote.id; emote.set_id = set_id; emote.srcSet = emote.urls[1] + " 1x"; @@ -374,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)(" + emote.name + ")(?=\\W|$)", "g"); + emote.regex = new RegExp("(^|\\W|\\b)(" + RegExp.escape(emote.name) + ")(?=\\W|$)", "g"); else - emote.regex = new RegExp("(^|\\W|\\b)(" + emote.name + ")\\b", "g"); + emote.regex = new RegExp("(^|\\W|\\b)(" + RegExp.escape(emote.name) + ")\\b", "g"); output_css += build_css(emote); data.count++; diff --git a/src/ext/api.js b/src/ext/api.js new file mode 100644 index 00000000..75e94850 --- /dev/null +++ b/src/ext/api.js @@ -0,0 +1,298 @@ +var FFZ = window.FrankerFaceZ, + utils = require('../utils'), + + + build_css = function(emote) { + if ( ! emote.margins && ! emote.css ) + return ""; + + return 'img[src="' + emote.urls[1] + '"]{' + (emote.margins ? 'margin:' + emote.margins + ';' : '') + (emote.css || "") + '}' + }; + + +// --------------------- +// API Constructor +// --------------------- + +var API = FFZ.API = function(instance, name, icon) { + 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.ffz._apis[this.id] = this; + + this.emote_sets = {}; + this.global_sets = []; + this.default_sets = []; + + this.on_room_callbacks = []; + + this.name = name || ("Extension#" + this.id); + this.icon = icon || null; + + this.ffz.log('Registered New Extension #' + this.id + ': ' + this.name); +}; + + +FFZ.prototype.api = function(name, icon) { + // Load the known APIs list. + if ( ! this._known_apis ) { + this._known_apis = {}; + if ( localStorage.hasOwnProperty('ffz_known_apis') ) + try { + this._known_apis = JSON.parse(localStorage.ffz_known_apis); + } catch(err) { + this.log("Error loading Known APIs: " + err); + } + } + + return new API(this, name, icon); +} + + +API.prototype.log = function(msg, data, to_json, log_json) { + this.ffz.log('Ext "' + this.name + '": ' + msg, data, to_json, log_json); +} + + +// --------------------- +// Set Loading +// --------------------- + +API.prototype._load_set = function(real_id, set_id, data) { + if ( ! data ) + return false; + + // Check for an existing set to copy the users. + var users = []; + if ( this.emote_sets[real_id] && this.emote_sets[real_id].users ) + users = this.emote_sets[real_id].users; + + var set = { + source: this.name, + source_ext: this.id, + source_id: set_id, + users: users, + count: 0, + emoticons: {}, + _type: data._type || 0, + css: data.css || null, + description: data.description || null, + icon: data.icon || this.icon || null, + id: real_id, + title: data.title || "Global Emoticons", + }; + + this.emote_sets[real_id] = set; + + if ( this.ffz.emote_sets ) + this.ffz.emote_sets[real_id] = set; + + var output_css = "", + ems = data.emoticons, + emoticons = set.emoticons; + + for(var i=0; i < ems.length; i++) { + var emote = ems[i], + new_emote = {urls: {}}, + id = emote.id || (this.name + '-' + set_id + '-' + i); + + if ( ! emote.name ) + continue; + + new_emote.id = id; + new_emote.set_id = real_id; + new_emote.name = emote.name; + + new_emote.width = emote.width; + new_emote.height = emote.height; + + new_emote.hidden = emote.hidden; + new_emote.owner = emote.owner; + + new_emote.css = emote.css; + new_emote.margins = emote.margins; + + new_emote.srcSet = emote.urls[1] + ' 1x'; + new_emote.urls[1] = emote.urls[1]; + + if ( emote.urls[2] ) { + new_emote.urls[2] = emote.urls[2]; + new_emote.srcSet += ', ' + emote.urls[2] + ' 2x'; + } + if ( emote.urls[3] ) { + new_emote.urls[3] = emote.urls[3]; + new_emote.srcSet += ', ' + emote.urls[3] + ' 3x'; + } + if ( emote.urls[4] ) { + new_emote.urls[4] = emote.urls[4]; + new_emote.srcSet += ', ' + emote.urls[4] + ' 4x'; + } + + if ( emote.regex ) + new_emote.regex = emote.regex; + 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"); + + output_css += build_css(new_emote); + set.count++; + emoticons[id] = new_emote; + } + + utils.update_css(this.ffz._emote_style, real_id, output_css + (set.css || "")); + + if ( this.ffz._cindex ) + this.ffz._cindex.ffzFixTitle(); + + try { + this.ffz.update_ui_link(); + } catch(err) { } + + return set; +} + + +// --------------------- +// Global Emote Sets +// --------------------- + +API.prototype.register_global_set = function(id, set) { + var exact_id = this.id + '-' + id; + + set.title = set.title || "Global Emoticons"; + set._type = 0; + set = this._load_set(exact_id, id, set); + + this.log("Loaded Emoticon Set #" + id + ": " + set.title + " (" + set.count + " emotes)", set); + + if ( this.global_sets.indexOf(exact_id) === -1 ) + this.global_sets.push(exact_id); + + if ( this.default_sets.indexOf(exact_id) === -1 ) + this.default_sets.push(exact_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); +}; + + +API.prototype.unregister_global_set = function(id) { + var exact_id = this.id + '-' + id, + set = this.emote_sets[exact_id]; + + if ( ! 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; + + var ind = this.global_sets.indexOf(exact_id); + if ( ind !== -1 ) + this.global_sets.splice(ind,1); + + ind = this.default_sets.indexOf(exact_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); + + this.log("Unloaded Emoticon Set #" + id + ": " + set.title, set);; +}; + + +// ----------------------- +// Per-Channel Emote Sets +// ----------------------- + +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); + }; + + if ( specific_func ) { + try { + specific_func(room_id, callback); + } catch(err) { + this.log("Error in On-Room Callback: " + err); + } + + } else { + for(var i=0; i < this.on_room_callbacks.length; i++) { + var cb = this.on_room_callbacks[i]; + try { + cb(room_id, callback); + } catch(err) { + this.log("Error in On-Room Callback: " + err); + } + } + } +} + + +API.prototype.register_on_room_callback = function(callback) { + this.on_room_callbacks.push(callback); + + // Call this for all current rooms. + if ( this.ffz.rooms ) { + for(var room_id in this.ffz.rooms) + this._room_callbacks(room_id, this.ffz.rooms[room_id], callback); + } +} + + +API.prototype.unregister_on_room_callback = function(callback) { + var ind = this.on_room_callbacks.indexOf(callback); + if ( ind !== -1 ) + this.on_room_callbacks.splice(ind, 1); +} \ No newline at end of file diff --git a/src/ext/betterttv.js b/src/ext/betterttv.js index 4a3369de..84d5d8af 100644 --- a/src/ext/betterttv.js +++ b/src/ext/betterttv.js @@ -69,6 +69,7 @@ FFZ.prototype.setup_bttv = function(delay) { document.body.classList.remove("ffz-chat-separator-wide"); document.body.classList.remove("ffz-chat-separator-3d-inset"); document.body.classList.remove("ffz-sidebar-swap"); + document.body.classList.remove("ffz-portrait"); document.body.classList.remove("ffz-flip-dashboard"); document.body.classList.remove("ffz-transparent-badges"); document.body.classList.remove("ffz-high-contrast-chat-text"); diff --git a/src/ext/emote_menu.js b/src/ext/emote_menu.js index 502e7880..80b83ec3 100644 --- a/src/ext/emote_menu.js +++ b/src/ext/emote_menu.js @@ -49,7 +49,7 @@ FFZ.prototype._emote_menu_enumerator = function() { if ( emote.hidden ) continue; - var title = "FrankerFaceZ " + set.title, + var title = (set.source || "FrankerFaceZ") + " " + set.title, badge = set.icon || '//cdn.frankerfacez.com/script/devicon.png'; emotes.push({text: emote.name, url: emote.urls[1], diff --git a/src/featurefriday.js b/src/featurefriday.js index e1cbe3ec..959a48e0 100644 --- a/src/featurefriday.js +++ b/src/featurefriday.js @@ -17,7 +17,7 @@ FFZ.prototype.check_ff = function(tries) { if ( ! tries ) this.log("Checking for Feature Friday data..."); - jQuery.ajax(constants.SERVER + "script/event.json", {dataType: "json", context: this}) + jQuery.ajax(constants.SERVER + "script/event.json?_=" + (constants.DEBUG ? Date.now() : FFZ.version_info), {dataType: "json", context: this}) .done(function(data) { return this._load_ff(data); }).fail(function(data) { @@ -44,7 +44,7 @@ FFZ.ws_commands.reload_ff = function() { // -------------------- FFZ.prototype._feature_friday_ui = function(room_id, parent, view) { - if ( ! this.feature_friday || this.feature_friday.channel == room_id ) + if ( ! this.feature_friday || this.feature_friday.channel === room_id ) return; this._emotes_for_sets(parent, view, [this.feature_friday.set], this.feature_friday.title, this.feature_friday.icon, "FrankerFaceZ"); @@ -52,7 +52,7 @@ FFZ.prototype._feature_friday_ui = function(room_id, parent, view) { // Before we add the button, make sure the channel isn't the // current channel. var Channel = App.__container__.lookup('controller:channel'); - if ( Channel && Channel.get('id') == this.feature_friday.channel ) + if ( ! this.feature_friday.channel || (Channel && Channel.get('id') === this.feature_friday.channel) ) return; @@ -98,13 +98,14 @@ FFZ.prototype._load_ff = function(data) { } // If there's no data, just leave. - if ( ! data || ! data.set || ! data.channel ) + if ( ! data || ! data.set ) return; // We have our data! Set it up. this.feature_friday = {set: data.set, channel: data.channel, live: false, title: data.title || "Feature Friday", - display_name: FFZ.get_capitalization(data.channel, this._update_ff_name.bind(this))}; + icon: data.icon, + display_name: data.channel ? FFZ.get_capitalization(data.channel, this._update_ff_name.bind(this)) : data.title || "Feature Friday"}; // Add the set. this.global_sets.push(data.set); @@ -117,7 +118,7 @@ FFZ.prototype._load_ff = function(data) { FFZ.prototype._update_ff_live = function() { - if ( ! this.feature_friday ) + if ( ! this.feature_friday || ! this.feature_friday.channel ) return; var f = this; diff --git a/src/main.js b/src/main.js index 94f78330..6c655f70 100644 --- a/src/main.js +++ b/src/main.js @@ -10,6 +10,7 @@ var FFZ = window.FrankerFaceZ = function() { // Logging this._log_data = []; + this._apis = {}; // Get things started. this.initialize(); @@ -21,7 +22,7 @@ FFZ.get = function() { return FFZ.instance; } // Version var VER = FFZ.version_info = { - major: 3, minor: 5, revision: 30, + major: 3, minor: 5, revision: 36, toString: function() { return [VER.major, VER.minor, VER.revision].join(".") + (VER.extra || ""); } @@ -115,7 +116,7 @@ require('./tokenize'); //require('./filtering'); -// Analytics: require('./ember/router'); +require('./ember/router'); require('./ember/channel'); require('./ember/player'); require('./ember/room'); @@ -149,6 +150,7 @@ require('./ui/my_emotes'); require('./ui/about_page'); require('./commands'); +require('./ext/api'); // --------------- @@ -322,7 +324,7 @@ FFZ.prototype.init_ember = function(delay) { this.setup_emoticons(); this.setup_badges(); - //this.setup_router(); + this.setup_router(); this.setup_colors(); this.setup_tokenization(); //this.setup_filtering(); diff --git a/src/settings.js b/src/settings.js index d030b343..facd1ec5 100644 --- a/src/settings.js +++ b/src/settings.js @@ -53,32 +53,18 @@ FFZ.prototype.load_settings = function() { // Build a settings object. this.settings = {}; - for(var key in FFZ.settings_info) { - if ( ! FFZ.settings_info.hasOwnProperty(key) ) - continue; - - var info = FFZ.settings_info[key], - ls_key = info.storage_key || make_ls(key), - val = info.hasOwnProperty("value") ? info.value : undefined; - - if ( localStorage.hasOwnProperty(ls_key) ) { - try { - val = JSON.parse(localStorage.getItem(ls_key)); - } catch(err) { - this.log('Error loading value for "' + key + '": ' + err); - } - } - - if ( info.process_value ) - val = info.process_value.bind(this)(val); - - this.settings[key] = val; - } - // Helpers this.settings.get = this._setting_get.bind(this); this.settings.set = this._setting_set.bind(this); this.settings.del = this._setting_del.bind(this); + this.settings.load = this._setting_load.bind(this); + + for(var key in FFZ.settings_info) { + if ( ! FFZ.settings_info.hasOwnProperty(key) ) + continue; + + this._setting_load(key); + } // Listen for Changes window.addEventListener("storage", this._setting_update.bind(this), false); @@ -825,6 +811,26 @@ FFZ.prototype._setting_update = function(e) { // Settings Access // -------------------- +FFZ.prototype._setting_load = function(key, default_value) { + var info = FFZ.settings_info[key], + ls_key = info && info.storage_key || make_ls(key), + val = default_value || (info && info.hasOwnProperty("value") ? info.value : undefined); + + if ( localStorage.hasOwnProperty(ls_key) ) { + try { + val = JSON.parse(localStorage.getItem(ls_key)); + } catch(err) { + this.log('Error loading value for "' + key + '": ' + err); + } + } + + if ( info && info.process_value ) + val = info.process_value.bind(this)(val); + + this.settings[key] = val; +} + + FFZ.prototype._setting_get = function(key) { return this.settings[key]; } diff --git a/src/socket.js b/src/socket.js index 6263c280..7739a086 100644 --- a/src/socket.js +++ b/src/socket.js @@ -29,6 +29,9 @@ FFZ.prototype.ws_iframe = function() { FFZ.prototype.ws_create = function() { + // Disable sockets for now. + return; + var f = this, ws; this._ws_last_req = 0; diff --git a/src/tokenize.js b/src/tokenize.js index e24c4b46..618b43c3 100644 --- a/src/tokenize.js +++ b/src/tokenize.js @@ -543,7 +543,7 @@ FFZ.prototype.render_tokens = function(tokens, render_links) { tooltip = emote ? utils.sanitize(f._emote_tooltip(emote)) : token.altText; srcset = emote ? emote.srcSet : token.srcSet; - extra = ' data-ffz-emote="' + emote.id + '"'; + extra = (emote ? ' data-ffz-emote="' + emote.id + '"' : '') + (emote_set ? ' data-ffz-set="' + emote_set.id + '"' : ''); } else if ( token.ffzEmoji ) { var eid = token.ffzEmoji, diff --git a/src/ui/menu.js b/src/ui/menu.js index c1c54be5..7e135abb 100644 --- a/src/ui/menu.js +++ b/src/ui/menu.js @@ -5,7 +5,7 @@ var FFZ = window.FrankerFaceZ, TWITCH_BASE = "http://static-cdn.jtvnw.net/emoticons/v1/", fix_menu_position = function(container) { - var swapped = document.body.classList.contains('ffz-sidebar-swap'); + var swapped = document.body.classList.contains('ffz-sidebar-swap') && ! document.body.classList.contains('ffz-portrait'); var bounds = container.getBoundingClientRect(), left = parseInt(container.style.left || '0'), @@ -419,8 +419,11 @@ FFZ.menu_pages.channel = { grid.className = "emoticon-grid"; header.className = "heading"; - if ( icon ) + if ( icon ) { header.style.backgroundImage = 'url("' + icon + '")'; + if ( icon.indexOf('.svg') !== -1 ) + header.style.backgroundSize = '18px'; + } header.innerHTML = 'TwitchSubscriber Emoticons'; grid.appendChild(header); @@ -507,7 +510,7 @@ FFZ.menu_pages.channel = { } // Do we have extra sets? - var extra_sets = room && room.extra_sets || []; + var extra_sets = _.union(room && room.extra_sets || [], room && room.ext_sets || [], []); // Basic Emote Sets this._emotes_for_sets(inner, view, room && room.set && [room.set] || [], (this.feature_friday || has_product || extra_sets.length ) ? "Channel Emoticons" : null, "http://cdn.frankerfacez.com/script/devicon.png", "FrankerFaceZ"); @@ -515,9 +518,9 @@ FFZ.menu_pages.channel = { for(var i=0; i < extra_sets.length; i++) { // Look up the set name. var set = this.emote_sets[extra_sets[i]], - name = set ? "Featured " + set.title : "Featured Channel"; + name = set ? (set.hasOwnProperty('source_ext') ? "" : "Featured ") + set.title : "Featured Channel"; - this._emotes_for_sets(inner, view, [extra_sets[i]], name, "http://cdn.frankerfacez.com/script/devicon.png", "FrankerFaceZ"); + this._emotes_for_sets(inner, view, [extra_sets[i]], name, set.icon || "//cdn.frankerfacez.com/script/devicon.png", set.source || "FrankerFaceZ"); } // Feature Friday! @@ -550,8 +553,11 @@ FFZ.prototype._emotes_for_sets = function(parent, view, sets, header, image, sub el_header.appendChild(document.createTextNode(header)); - if ( image ) + if ( image ) { el_header.style.backgroundImage = 'url("' + image + '")'; + if ( image.indexOf('.svg') !== -1 ) + el_header.style.backgroundSize = '18px'; + } grid.appendChild(el_header); } @@ -610,7 +616,7 @@ 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 ) + if ( (e.shiftKey || e.shiftLeft) && f.settings.clickable_emoticons && ! set.hasOwnProperty('source_ext') ) window.open("https://www.frankerfacez.com/emoticons/" + id); else this._add_emote(view, code); diff --git a/src/ui/my_emotes.js b/src/ui/my_emotes.js index 8606cffe..2379bbec 100644 --- a/src/ui/my_emotes.js +++ b/src/ui/my_emotes.js @@ -269,17 +269,23 @@ FFZ.menu_pages.myemotes = { var heading = document.createElement('div'), menu = document.createElement('div'), f = this, - emotes = []; + emotes = [], + + menu_id = set.hasOwnProperty('source_ext') ? 'ffz-ext-' + set.source_ext + '-' + set.source_id : 'ffz-' + set.id, + icon = set.icon || (set.hasOwnProperty('source_ext') && '//cdn.frankerfacez.com/emoji/tw-1f4ac.svg') || '//cdn.frankerfacez.com/script/devicon.png'; heading.className = 'heading'; - heading.innerHTML = 'FrankerFaceZ' + set.title; - heading.style.backgroundImage = 'url("' + (set.icon || '//cdn.frankerfacez.com/script/devicon.png') + '")'; + heading.innerHTML = '' + (utils.sanitize(set.source) || 'FrankerFaceZ') + '' + set.title; + + heading.style.backgroundImage = 'url("' + icon + '")'; + if ( icon.indexOf('.svg') !== -1 ) + heading.style.backgroundSize = "18px"; menu.className = 'emoticon-grid collapsable'; menu.appendChild(heading); - menu.setAttribute('data-set', 'ffz-' + set.id); - menu.classList.toggle('collapsed', this.settings.emote_menu_collapsed.indexOf('ffz-' + set.id) !== -1); + menu.setAttribute('data-set', menu_id); + menu.classList.toggle('collapsed', this.settings.emote_menu_collapsed.indexOf(menu_id) !== -1); heading.addEventListener('click', function() { FFZ.menu_pages.myemotes.toggle_section.bind(f)(this); }); for(var emote_id in set.emoticons) @@ -325,7 +331,7 @@ 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 ) + if ( (e.shiftKey || e.shiftLeft) && f.settings.clickable_emoticons && ! set.hasOwnProperty('source_ext') ) window.open("https://www.frankerfacez.com/emoticons/" + id); else this._add_emote(view, code); diff --git a/style.css b/style.css index 5cd812e0..651dd885 100644 --- a/style.css +++ b/style.css @@ -1673,7 +1673,11 @@ body.ffz-minimal-chat-input .ember-chat .chat-interface .textarea-contain textar /* Swap Sidebars */ -.ffz-sidebar-swap .ember-chat .chat-interface .emoticon-selector { +body[data-current-path^="user."].ffz-portrait #right_close { transform: rotate(90deg); } +body[data-current-path^="user."].ffz-portrait .archives-contain .more-archives { width: 100%; } + +body:not([data-current-path^="user."]) .ffz-sidebar-swap .ember-chat .chat-interface .emoticon-selector, +.ffz-sidebar-swap:not(.ffz-portrait) .ember-chat .chat-interface .emoticon-selector { right: auto; left: 20px; } @@ -1688,7 +1692,7 @@ body.ffz-minimal-chat-input .ember-chat .chat-interface .textarea-contain textar left: 0; } -.ffz-sidebar-swap #right_close, +.ffz-sidebar-swap:not(.ffz-portrait) #right_close, .ffz-sidebar-swap #left_close { transform: scaleX(-1); } From acc0010247adc899d4b985946bf1c5d90a8fbb35 Mon Sep 17 00:00:00 2001 From: SirStendec Date: Tue, 27 Oct 2015 14:25:13 -0400 Subject: [PATCH 2/3] 3.5.37 to 3.5.40. Added Twitch emote mirror. Fixed emote sorting in My Emoticons. Fixed failure to leave chat rooms. Added /ffz reload chat command. Fixed Stream Latency again. Added new API methods. Made settings.get try to load unloaded settings. --- src/badges.js | 22 +++--- src/commands.js | 62 ++++++++++++++-- src/constants.js | 2 + src/ember/channel.js | 5 +- src/ember/chatview.js | 2 +- src/ember/line.js | 17 ++++- src/ember/player.js | 25 +++++-- src/emoticons.js | 4 +- src/ext/api.js | 160 +++++++++++++++++++++++++++++++++--------- src/main.js | 2 +- src/settings.js | 4 ++ src/tokenize.js | 22 +++++- src/ui/menu.js | 14 +++- src/ui/my_emotes.js | 19 +++-- src/utils.js | 8 ++- 15 files changed, 295 insertions(+), 73 deletions(-) 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 From a7f6e0b63fd519c2cdf118961d9f4c3df942272a Mon Sep 17 00:00:00 2001 From: SirStendec Date: Tue, 27 Oct 2015 21:22:54 -0400 Subject: [PATCH 3/3] Removed Firefox update warning. Made styles attempt to re-downlaod with new URLs upon error due to CDN issues. Made viewer list sorting optional. Added safety checks to API for non-global sets. --- src/ember/viewers.js | 17 +++++++++++++++++ src/ext/api.js | 9 +++++++++ src/main.js | 2 +- src/ui/dark.js | 1 + src/ui/notifications.js | 12 ------------ src/ui/styles.js | 2 ++ 6 files changed, 30 insertions(+), 13 deletions(-) diff --git a/src/ember/viewers.js b/src/ember/viewers.js index fe17fefa..2aab88c0 100644 --- a/src/ember/viewers.js +++ b/src/ember/viewers.js @@ -1,6 +1,20 @@ var FFZ = window.FrankerFaceZ; +// -------------------- +// Settings +// -------------------- + +FFZ.settings_info.sort_viewers = { + type: "boolean", + value: true, + + category: "Chat Appearance", + name: "Sort Viewer List", + help: "Make sure the viewer list is alphabetically sorted and place the Broadcaster in their own category." +}; + + // -------------------- // Initialization // -------------------- @@ -19,6 +33,9 @@ FFZ.prototype._modify_viewers = function(controller) { controller.reopen({ lines: function() { var viewers = this._super(); + if ( ! f.settings.sort_viewers ) + return viewers; + try { var categories = [], data = {}, diff --git a/src/ext/api.js b/src/ext/api.js index 5c720003..83cfb267 100644 --- a/src/ext/api.js +++ b/src/ext/api.js @@ -259,6 +259,11 @@ API.prototype.register_global_set = function(id, 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; + + // 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); @@ -323,6 +328,10 @@ API.prototype.register_room_set = function(room_id, id, emote_set) { 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. room.ext_sets.push(exact_id); emote_set.users.push(room_id); diff --git a/src/main.js b/src/main.js index df2f440f..655e8d6f 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: 40, + major: 3, minor: 5, revision: 42, toString: function() { return [VER.major, VER.minor, VER.revision].join(".") + (VER.extra || ""); } diff --git a/src/ui/dark.js b/src/ui/dark.js index 6de8f3bd..a625892e 100644 --- a/src/ui/dark.js +++ b/src/ui/dark.js @@ -211,5 +211,6 @@ FFZ.prototype._load_dark_css = function() { s.id = "ffz-dark-css"; s.setAttribute('rel', 'stylesheet'); s.setAttribute('href', constants.SERVER + "script/dark.css?_=" + (constants.DEBUG ? Date.now() : FFZ.version_info)); + s.onerror = "this.href = this.href + '_';" document.head.appendChild(s); } \ No newline at end of file diff --git a/src/ui/notifications.js b/src/ui/notifications.js index 7e17d95a..6a2b5b37 100644 --- a/src/ui/notifications.js +++ b/src/ui/notifications.js @@ -8,18 +8,6 @@ var FFZ = window.FrankerFaceZ; FFZ.prototype.setup_notifications = function() { this.log("Adding event handler for window focus."); window.addEventListener("focus", this.clear_notifications.bind(this)); - - // Firefox update warning. - if ( navigator.userAgent.toLowerCase().indexOf('firefox/') !== -1 ) { - if ( localStorage.hasOwnProperty('ffz_mozilla_sign_warning') ) - return; - - localStorage.ffz_mozilla_sign_warning = true; - var f = this; - setTimeout(function() { - f.show_message('Firefox users please re-download the add-on from https://www.frankerfacez.com/ to ensure that it continues to function after the upgrade to Firefox 41. You should see version 1.56 in your add-ons listing.'); - }, 1000); - } } diff --git a/src/ui/styles.js b/src/ui/styles.js index b21f008d..d8312d29 100644 --- a/src/ui/styles.js +++ b/src/ui/styles.js @@ -11,6 +11,8 @@ FFZ.prototype.setup_css = function() { s.id = "ffz-ui-css"; s.setAttribute('rel', 'stylesheet'); s.setAttribute('href', constants.SERVER + "script/style.css?_=" + (constants.DEBUG ? Date.now() : FFZ.version_info)); + s.onerror = "this.href = this.href + '_';" + document.head.appendChild(s); if ( window.jQuery && jQuery.noty )