diff --git a/changelog.html b/changelog.html index 682bc724..47ba71d3 100644 --- a/changelog.html +++ b/changelog.html @@ -1,59 +1,79 @@ -
game
Possible Values: " + output.join(", "),
+ "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 ) @@ -514,9 +514,10 @@ FFZ.prototype.get_twitch_badges = function(badge_tag, room_id) { continue; var versions = channel[badge] || globals[badge], - binfo = versions && versions.versions && versions.versions[version]; + binfo = versions && versions.versions && versions.versions[version], + is_game = badge.substr(-2) === '_1'; - if ( hidden_badges.indexOf(badge) !== -1 ) + if ( hidden_badges.indexOf(badge) !== -1 || (is_game && hidden_badges.indexOf('game') !== -1) ) continue; if ( BADGE_POSITIONS.hasOwnProperty(badge) ) @@ -648,7 +649,8 @@ FFZ.prototype.bttv_badges = function(data) { hidden_key = hidden_key.substr(0, ind); } - if ( hidden_badges.indexOf(hidden_key) !== -1 ) { + 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; diff --git a/src/constants.js b/src/constants.js index acd39773..f9d73dc0 100644 --- a/src/constants.js +++ b/src/constants.js @@ -26,6 +26,11 @@ module.exports = FrankerFaceZ.constants = { IS_WEBKIT: IS_WEBKIT, META_NAME: IS_OSX ? "⌘" : (IS_WIN ? "Win" : "Meta"), + // ITAD Stuff + ITAD_SERVER: "https://api.isthereanydeal.com/", + ITAD_CLIENT_ID: "ffdd6ec4951be145", + ITAD_API_KEY: "43667479346a55e9b473f605a6b07ede02c8f3b2", + // Twitch Client ID for API Stuff CLIENT_ID: "a3bc9znoz6vi8ozsoca0inlcr4fcvkl", diff --git a/src/ember/bits.js b/src/ember/bits.js index 1cfc8d87..c799f8be 100644 --- a/src/ember/bits.js +++ b/src/ember/bits.js @@ -90,21 +90,29 @@ FFZ.settings_info.bits_pinned = { } -FFZ.settings_info.bits_disable_charity = { +FFZ.settings_info.bits_redesign = { type: "boolean", value: false, - category: "Chat Filtering", - name: "Disable Cheering with #Charity Notices", - help: "Stop displaying Twitch's notices about Cheering with #Charity." -} + category: "Chat Appearance", + name: "Bits Redesign", + help: "Use the special cheering animations from April 1st, 2017.", + on_update: function() { + var bits = utils.ember_lookup('service:bits-emotes') || + utils.ember_lookup('service:bits-rendering-config'); + if ( bits && bits.ffz_has_css ) + bits.ffz_update_css(); + } +} // -------------------- // Initialization // -------------------- +var redesign = function(x) { return x.replace('/actions/cheer/', '/actions/cheer-redesign/') }; + FFZ.prototype.setup_bits = function() { utils.toggle_cls('ffz-show-bits-tags')(this.settings.bits_tags_container); @@ -115,9 +123,19 @@ FFZ.prototype.setup_bits = function() { PinnedCheers = utils.ember_lookup('service:bits-pinned-cheers'), image_css = function(images) { - return 'background-image: url("' + images[1] + '");' + + var im_1 = images[1], + im_2 = images[2], + im_4 = images[4]; + + if ( f.settings.bits_redesign ) { + im_1 = redesign(im_1); + im_2 = redesign(im_2); + im_4 = redesign(im_4); + } + + return 'background-image: url("' + im_1 + '");' + 'background-image: ' + (constants.IS_WEBKIT ? ' -webkit-' : '') + 'image-set(' + - 'url("' + images[1] + '") 1x, url("' + images[2] + '") 2x, url("' + images[4] + '") 4x);'; + 'url("' + im_1 + '") 1x, url("' + im_2 + '") 2x, url("' + im_4 + '") 4x);'; }, tier_css = function(ind, prefix, tier) { @@ -184,7 +202,8 @@ FFZ.prototype.setup_bits = function() { }, ffz_get_preview: function(prefix, amount) { - return this.getImageSrc(amount, prefix, true, !f.settings.bits_animated, 4); + var src = this.getImageSrc(amount, prefix, true, !f.settings.bits_animated, 4); + return f.settings.bits_redesign ? redesign(src) : src; }, _ffz_image_css: image_css, @@ -249,12 +268,14 @@ FFZ.prototype.setup_bits = function() { ffz_get_preview: function(prefix, amount) { var data = this.ffz_get_tier(prefix, amount), - tier = data && data[1]; - return tier ? this._constructImageSrc([4], tier, { - background: 'dark', - scale: 4, - state: f.settings.bits_animated ? 'animated' : 'static' - }).src : ''; + tier = data && data[1], + src = tier ? this._constructImageSrc([4], tier, { + background: 'dark', + scale: 4, + state: f.settings.bits_animated ? 'animated' : 'static' + }).src : ''; + + return f.settings.bits_redesign ? redesign(src) : src; }, _ffz_image_css: image_css, diff --git a/src/ember/commerce.js b/src/ember/commerce.js new file mode 100644 index 00000000..a36458ff --- /dev/null +++ b/src/ember/commerce.js @@ -0,0 +1,316 @@ +var FFZ = window.FrankerFaceZ, + utils = require('../utils'), + constants = require('../constants'); + + +// -------------------- +// Settings +// -------------------- + +FFZ.settings_info.show_commerce = { + type: "select", + options: { + 0: "Never", + 1: "When Revenue is Shared", + 2: "Always" + }, + + value: 2, + process_value: utils.process_int(0), + + no_mobile: true, + + category: "Commerce", + name: "Display Commerce Bar", + help: "Show the commerce bar under channels that allows you to purchase supported games.", + + on_update: function(val) { + utils.toggle_cls('ffz-hide-purchase-game')(val === 0); + var views = utils.ember_views(), + ChannelBox = utils.ember_resolve('component:commerce/channel-box'); + + if ( ! ChannelBox ) + return; + + for(var key in views) + if ( views[key] instanceof ChannelBox ) + try { + views[key].ffzUpdateVisibility(); + } catch(err) { } + } +} + + +FFZ.settings_info.show_itad = { + type: "boolean", + value: true, + + no_mobile: true, + + category: "Commerce", + name: "Display Competitor Pricing", + help: "Add a button on the commerce bar with pricing from other stores.", + + on_update: function(val) { + var views = utils.ember_views(), + BuyGameNow = utils.ember_resolve('component:commerce/buy-game-now'); + + if ( ! BuyGameNow ) + return; + + for(var key in views) + if ( views[key] instanceof BuyGameNow ) + try { + views[key].ffzRenderPricing(); + } catch(err) { } + } +} + + +// -------------------- +// Initialization +// -------------------- + +FFZ.prototype.setup_commerce = function() { + this._itad_game_to_plain = {}; + + // Styles + utils.toggle_cls('ffz-hide-purchase-game')(this.settings.show_commerce === 0); + + // Ember Modifications + this.update_views('component:commerce/channel-box', this.modify_commerce_box); + this.update_views('component:commerce/buy-game-now', this.modify_buy_game_now); +} + + +// -------------------- +// Modifications +// -------------------- + +FFZ.prototype.modify_commerce_box = function(view) { + var f = this; + utils.ember_reopen_view(view, { + ffz_init: function() { + this.ffzUpdateVisibility(); + }, + + ffzUpdateVisibility: function() { + var el = this.parentView.get('element'), + real_el = el && el.querySelector('.cmrc-channel-box'); + + if ( ! real_el ) + ! this.isDestroyed && setTimeout(this.ffzUpdateVisibility.bind(this), 250); + else + real_el.classList.toggle('hidden', f.settings.show_commerce === 1 && ! this.get('showSupports')); + + }.observes('showSupports') + }) +} + + +FFZ.prototype.modify_buy_game_now = function(view) { + var f = this; + utils.ember_reopen_view(view, { + itad_plain: null, + itad_price: null, + twitch_geo: null, + + ffz_init: function() { + //f.log("Buy-Game-New Component", this); + this.itad_count = 0; + this.ffzUpdateITADPlain(); + + var t = this; + Twitch.geo.then(function(data) { + t.set('twitch_geo', data && data.geo); + }); + }, + + ffzTitle: function() { + // Do this because Twitch's ToS say you're not allowed to use data collected from + // the API to show users commercial offers. This extracts the game title from the + // page itself and not from any kind of JS API. + + // Granted, they're probably more worried about automated chat spam and people + // sending spam to email addresses recovered from authenticated user profile requests. + + var el; + if ( document.body.dataset.currentPath === 'directory.game-details' ) + el = document.querySelector('.game-details__page-title'); + else + el = document.querySelector('.card__info [data-tt_content="current_game"]'); + + var output = el ? _.pluck(_.filter(el.childNodes, function(x) { return x.nodeType === document.TEXT_NODE }), 'textContent').join(' ').trim() : null; + + if ( ! output && this.itad_count < 50 ) { + var t = this; + setTimeout(function() { + t.itad_count += 1; + Ember.propertyDidChange(this, 'ffzTitle'); + }, 250); + } + + return output; + + }.property(), + + didReceiveAttrs: function() { + this._super(); + Ember.propertyDidChange(this, 'ffzTitle'); + }, + + ffzUpdateITADPlain: function() { + var title = this.get('ffzTitle'), + old_plain = this.get('itad_plain'), + plain = f._itad_game_to_plain[title] || null; + + //f.log("Update ITAD Plain: " + title + " -- [" + plain + "]", this); + + // If we already have the value, fetch it now. + if ( ! title || plain ) { + if ( old_plain !== plain ) + this.set('itad_plain', plain); + return; + } + + if ( old_plain ) + this.set('itad_plain', null); + + var t = this; + f.ws_send("get_itad_plain", title, function(success, data) { + if ( ! success ) return; + + f._itad_game_to_plain[title] = data; + t.ffzUpdateITADPlain(); + }, true); + + }.observes('ffzTitle'), + + ffzUpdateITADPrice: function() { + var t = this, + old_price = this.get('itad_price'), + geo = this.get('twitch_geo'), + plain = this.get('itad_plain'); + + if ( old_price && old_price[0] === plain ) + return; + + if ( ! plain || ! geo ) + return this.set('itad_price', null); + + this.set('itad_price', [plain, null]); + f.ws_send("get_itad_prices", [plain, geo], function(success, data) { + if ( ! success ) return; + + t.set('itad_price', [plain, data]); + }); + + }.observes('itad_plain', 'twitch_geo'), + + + ffzRenderPricing: function() { + var t = this, + el = this.get('element'), + cont = el && el.querySelector('.ffz-price-info'), + btn_price, + data = this.get('itad_price'); + + if ( ! f.settings.show_itad || ! data || ! data[1] || ! data[1].list || ! data[1].list.length ) { + if ( cont ) + jQuery(cont).remove(); + return; + } + + if ( ! cont ) { + cont = utils.createElement('div', 'ffz-price-info mg-l-1 balloon-wrapper'); + btn_price = utils.createElement('span', 'ffz-price-num button__num-block pd-x-1 mg-1-0'); + + var btn = utils.createElement('button', 'button itad-button button--dropmenu', + utils.createElement('span', 'ffz-price-label inline-block pd-r-1', 'ITAD')); + + btn.appendChild(btn_price); + cont.appendChild(btn); + el.appendChild(cont); + + btn.addEventListener('click', function(event) { + t.ffzRenderPopup(event); + }); + + } else + btn_price = cont.querySelector('.ffz-price-num'); + + // Determine the cheapest price. + var sales = data[1].list, + cheapest = sales[0].price_new, + + currency = data[1].currency, + formatter = new Intl.NumberFormat(undefined, (currency && currency.code) ? {style: 'currency', currency: currency.code, minimumFractionDigits: 2} : {minimumFractionDigits: 2}); + + btn_price.textContent = formatter.format(cheapest); + + }.observes('itad_price'), + + ffzRenderPopup: function(e) { + if ( e.button !== 0 || e.altKey || e.ctrlKey || e.shiftKey || e.metaKey ) + return; + + e.preventDefault(); + e.stopPropagation(); + + var popup = f._popup ? f.close_popup() : f._last_popup, + t = this, + data = t.get('itad_price'), + el = this.get('element'), + cont = el && el.querySelector('.ffz-price-info'); + + if ( popup && popup.id === 'ffz-price-popup' || ! data || ! data[1] || ! data[1].list || ! data[1].list.length ) + return; + + var balloon = utils.createElement('div', 'itad-balloon balloon balloon--md show', '
Store | Price Cut | Current | Regular |
---|
Default: 60", - this.settings.notification_timeout, + "Please enter the time you'd like notifications to be displayed before automatically closing, in seconds. A value of zero may prevent notifications from disappearing automatically. This value is subject to the limitations of your web browser.
Default: 60", + old_val === false ? 0 : old_val, function(new_val) { if ( new_val === null || new_val === undefined ) return; new_val = parseInt(new_val); - if ( Number.isNaN(new_val) || ! Number.isFinite(new_val) || new_val < 1 ) + if ( new_val <= 0 ) + new_val = false; + else if ( Number.isNaN(new_val) || ! Number.isFinite(new_val) ) new_val = 60; f.settings.set("notification_timeout", new_val); @@ -135,7 +138,7 @@ FFZ.prototype.show_notification = function(message, title, tag, timeout, on_clic if ( perm === "granted" ) { title = title || "FrankerFaceZ"; - timeout = timeout || (this.settings.notification_timeout*1000); + timeout = timeout || (this.settings.notification_timeout === false ? false : this.settings.notification_timeout * 1000); var options = { lang: "en-US", diff --git a/style.css b/style.css index 1824ea0d..aefc218f 100644 --- a/style.css +++ b/style.css @@ -34,6 +34,7 @@ body > div.tipsy .tipsy-arrow { opacity: 0.8; } body:not(.ffz-theater-basic-stats) .player-userinfo__game, body:not(.ffz-theater-basic-stats) .player-controls-top, .moderation-card__actions:not(.loading):empty, +.ffz-hide-purchase-game .cmrc-channel-box, .ffz-minimal-dashboard[data-current-path="user.dashboards.index"] #left_col, .ffz-minimal-dashboard[data-current-path="user.dashboards.index"] #left_close, .ffz-minimal-dashboard[data-current-path="user.dashboards.index"] #right_col, @@ -615,6 +616,7 @@ body.ffz-bttv-dark .ffz-ui-toggle.blue.live:hover svg.svg-emoticons path { fill: .favorites-grid:not(:empty) + .ffz-no-emotes, .favorites-grid .emoticon.ffz-favorite:before { display: none } +.tipsy .ffz-favorite, .suggestion.ffz-favorite:before, .emoticon.ffz-favorite:before { content: ""; @@ -625,6 +627,8 @@ body.ffz-bttv-dark .ffz-ui-toggle.blue.live:hover svg.svg-emoticons path { fill: background: url("//cdn.frankerfacez.com/script/emoticon_favorite.png") no-repeat; } +.tipsy .ffz-favorite { right: 10px; bottom: 10px } + .suggestion.ffz-favorite:before { right: 2.5px; bottom: 2.5px } .chat-menu.ffz-ui-popup .ffz-ui-menu-page .chat-menu-content.menu-side-padding { padding-left: 20px; padding-right: 20px; } @@ -2015,6 +2019,8 @@ body.ffz-minimal-chat-input .ember-chat .chat-interface { padding: 0; } +body.ffz-minimal-chat-input .ember-chat .chat-messages .chat-lines { padding-bottom: 0 } + body.ffz-minimal-chat-input .ember-chat .chat-interface .textarea-contain { top: 0 !important; margin: 0 !important; @@ -2032,6 +2038,7 @@ body.ffz-minimal-chat-input .ember-chat .chat-interface .textarea-contain textar /* Chat Pause */ .ember-chat .chat-interface .more-messages-indicator.ffz-freeze-indicator { + z-index: 10; opacity: 1; cursor: default; pointer-events: none; @@ -3551,6 +3558,11 @@ body.ffz-bttv #ffz-feed-tabs .tabs { margin-bottom: 0 } padding-top: 5px; } +.ffz-baseline-emoticons .activity-body .emoticon.emoji, +.ffz-baseline-emoticons .chat-line .emoticon.emoji { + padding-top: 0px; +} + /* Settings Filtering */ @@ -3731,6 +3743,7 @@ div.metadata-box { .cn-metabar__ffz[data-key="viewers"] svg { fill: #fc3636 } .cn-metabar__ffz[data-key="viewers"] { color: #fc3636 } +.ffz-dark .pl-button--hollow:hover, .ffz-dark .button--hollow:hover, .cn-hosting--bottom .button-hollow { background-color: #2c2541; @@ -4019,4 +4032,58 @@ body.ffz-sidebar-swap .app-main.theatre #main_col:not(.expandRight) #player[data .dash-column { margin-right: calc(1.5rem - 5px); } -} \ No newline at end of file +} + + +/* ITAD */ + +.button.button--large + .ffz-price-info > .itad-button { + font-size: 1.6rem; + line-height: 3.6rem; + padding: 0 1.2rem; +} + +.itad-button { + background-color: #046eb2; + overflow: hidden; +} + +.itad-button .button__num-block { + margin: 0 -3rem 0 0; + padding-right: 3rem !important; + display: inline-block; +} + +.button.button--large + .ffz-price-info > .itad-button .button__num-block { + padding-right: 4.8rem !important; +} + +.itad-button:focus, +.itad-button:hover { + background-color: #1587cf; +} + +.itad-button:focus { box-shadow: 0 0 6px 0 #5c99bf, inset 0 0 0 1px #9ec2d9 } + +.itad-balloon table { + width: 100%; + border-spacing: 0; +} + +.itad-balloon th { border-bottom: 1px solid rgba(0,0,0,0.2) } +.itad-balloon td { padding: 4px 0 0 } + +.itad-balloon span { + display: block; + border-top: 1px solid rgba(0,0,0,0.2); + margin-top: 5px; + padding-top: 5px; +} + +.ffz-dark .itad-balloon th, +.ffz-dark .itad-balloon span { + border-color: rgba(255,255,255,0.2); +} + +.itad-balloon table th:not(:first-child), +.itad-balloon table td:not(:first-child) { text-align: right } \ No newline at end of file