1
0
Fork 0
mirror of https://github.com/FrankerFaceZ/FrankerFaceZ.git synced 2025-07-03 17:48:30 +00:00
* Added: Display bits emote rewards in the FFZ Emote Menu alongside subscription emotes.
* Changed: 2FA reward emotes are now labelled as such in their tool-tips.
* Changed: Update the style for the chat pause notice at the bottom of chat to match the latest Twitch style and language.
* Fixed: Disabling hosting not working on Mod View.
* Fixed: Emote sets not being sorted correctly in the FFZ Emote Menu when combining multiple sets (such as multiple subscription tiers) into one section.
* API Changed: The `setting-color-box` component now allows you to open the color picker up and not only down.
This commit is contained in:
SirStendec 2020-04-03 19:30:28 -04:00
parent 99eee7396d
commit 30b9a15c0d
12 changed files with 191 additions and 63 deletions

View file

@ -1,7 +1,7 @@
{ {
"name": "frankerfacez", "name": "frankerfacez",
"author": "Dan Salvato LLC", "author": "Dan Salvato LLC",
"version": "4.19.5", "version": "4.19.6",
"description": "FrankerFaceZ is a Twitch enhancement suite.", "description": "FrankerFaceZ is a Twitch enhancement suite.",
"license": "Apache-2.0", "license": "Apache-2.0",
"scripts": { "scripts": {

View file

@ -3,8 +3,8 @@
"name": "New API Stress Testing", "name": "New API Stress Testing",
"description": "Send duplicate requests to the new API server for load testing.", "description": "Send duplicate requests to the new API server for load testing.",
"groups": [ "groups": [
{"value": true, "weight": 75}, {"value": true, "weight": 50},
{"value": false, "weight": 25} {"value": false, "weight": 50}
] ]
} }
} }

View file

@ -963,6 +963,9 @@ function determineEmoteType(emote) {
if ( emote.setID == 300238151 ) if ( emote.setID == 300238151 )
return EmoteTypes.ChannelPoints; return EmoteTypes.ChannelPoints;
if ( emote.setID == 300374282 )
return EmoteTypes.TwoFactor;
const id = parseInt(emote.setID, 10); const id = parseInt(emote.setID, 10);
if ( ! isNaN(id) && isFinite(id) && id >= 5e8 ) if ( ! isNaN(id) && isFinite(id) && id >= 5e8 )
return EmoteTypes.BitsTier; return EmoteTypes.BitsTier;
@ -983,6 +986,9 @@ function determineSetType(set) {
if ( TWITCH_PRIME_SETS.includes(id) ) if ( TWITCH_PRIME_SETS.includes(id) )
return EmoteTypes.Prime; return EmoteTypes.Prime;
if ( id == 300374282 )
return EmoteTypes.TwoFactor;
const owner = set.owner; const owner = set.owner;
if ( owner ) { if ( owner ) {
if ( owner.id == 139075904 ) if ( owner.id == 139075904 )

View file

@ -1059,6 +1059,9 @@ export const AddonEmotes = {
} else if ( type === EmoteTypes.Prime || type === EmoteTypes.Turbo ) } else if ( type === EmoteTypes.Prime || type === EmoteTypes.Turbo )
source = this.i18n.t('emote.prime', 'Twitch Prime'); source = this.i18n.t('emote.prime', 'Twitch Prime');
else if ( type === EmoteTypes.TwoFactor )
source = this.i18n.t('emote.2fa', 'Twitch 2FA Emote');
else if ( type === EmoteTypes.LimitedTime ) else if ( type === EmoteTypes.LimitedTime )
source = this.i18n.t('emote.limited', 'Limited-Time Only Emote'); source = this.i18n.t('emote.limited', 'Limited-Time Only Emote');

View file

@ -13,6 +13,7 @@
:id="item.full_key" :id="item.full_key"
ref="control" ref="control"
:alpha="alpha" :alpha="alpha"
:open-up="openUp"
:nullable="true" :nullable="true"
:value="color" :value="color"
@input="onInput" @input="onInput"
@ -73,6 +74,13 @@ export default {
return this.value; return this.value;
}, },
openUp() {
if ( this.item.openUp != null )
return this.item.openUp;
return false;
},
alpha() { alpha() {
if ( this.item.alpha != null ) if ( this.item.alpha != null )
return this.item.alpha; return this.item.alpha;

View file

@ -56,7 +56,7 @@ export default class Channel extends Module {
this.ChannelPage = this.fine.define( this.ChannelPage = this.fine.define(
'channel-page', 'channel-page',
n => (n.updateHost && n.updateChannel && n.state && has(n.state, 'hostedChannel')) || (n.getHostedChannelLogin && n.handleHostingChange) || (n.onChatHostingChange && n.state && has(n.state, 'hostMode')), n => (n.updateHost && n.updateChannel && n.state && has(n.state, 'hostedChannel')) || (n.getHostedChannelLogin && n.handleHostingChange) || (n.onChatHostingChange && n.state && has(n.state, 'hostMode')),
['user', 'video', 'user-video', 'user-clip', 'user-videos', 'user-clips', 'user-collections', 'user-events', 'user-followers', 'user-following'] ['user', 'video', 'user-video', 'user-clip', 'user-videos', 'user-clips', 'user-collections', 'user-events', 'user-followers', 'user-following', 'mod-view']
); );
this.RaidController = this.fine.define( this.RaidController = this.fine.define(

View file

@ -6,6 +6,7 @@
import Module from 'utilities/module'; import Module from 'utilities/module';
import {get} from 'utilities/object'; import {get} from 'utilities/object';
import {createElement} from 'utilities/dom';
//import CHANNEL_QUERY from './channel_header_query.gql'; //import CHANNEL_QUERY from './channel_header_query.gql';
@ -57,6 +58,12 @@ export default class ChannelBar extends Module {
n => n.getTitle && n.getGame && n.renderGame, n => n.getTitle && n.getGame && n.renderGame,
['user'] ['user']
); );
this.ModWidget = this.fine.define(
'mod-widget',
n => n.renderToolbar && n.getToolbarControls && n.childContext,
['mod-view']
);
} }
onEnable() { onEnable() {
@ -77,6 +84,15 @@ export default class ChannelBar extends Module {
}); });
/*this.ModWidget.on('mount', this.updateModWidget, this);
this.ModWidget.on('update', this.updateModWidget, this);
this.ModWidget.ready((cls, instances) => {
for(const inst of instances)
this.updateModWidget(inst);
});*/
//this.VideoBar.on('unmount', this.unmountVideoBar, this); //this.VideoBar.on('unmount', this.unmountVideoBar, this);
this.VideoBar.on('mount', this.updateVideoBar, this); this.VideoBar.on('mount', this.updateVideoBar, this);
this.VideoBar.on('update', this.updateVideoBar, this); this.VideoBar.on('update', this.updateVideoBar, this);
@ -89,6 +105,30 @@ export default class ChannelBar extends Module {
} }
/*updateModWidget(inst) {
const container = this.fine.getChildNode(inst);
if ( ! container || ! container.querySelector('.video-player-hosting-ui__container') )
return;
const header = container.querySelector('.mod-view-panel-header');
if ( ! header )
return;
let cont = header.querySelector('.ffz--stat-container');
if ( ! cont ) {
cont = <div class="ffz--stat-container tw-pd-l-05"></div>;
const contcont = header.querySelector(':scope > div:first-child > div');
if ( ! contcont )
return;
contcont.appendChild(cont);
}
this.log.info('mod-widget', inst, cont);
}*/
updateVideoBar(inst) { updateVideoBar(inst) {
const container = this.fine.getChildNode(inst), const container = this.fine.getChildNode(inst),
timestamp = container && container.querySelector('[data-test-selector="date"]'); timestamp = container && container.querySelector('[data-test-selector="date"]');

View file

@ -637,11 +637,17 @@ export default class EmoteMenu extends Module {
return; return;
const locked = emote.locked && (! lock || ! lock.emotes.has(emote.id)), const locked = emote.locked && (! lock || ! lock.emotes.has(emote.id)),
emote_lock = locked && data.locks && data.locks[emote.set_id], emote_lock = locked && data.locks && data.locks[emote.set_id];
sellout = emote_lock ? (data.all_locked ? let sellout = '';
t.i18n.t('emote-menu.emote-sub', 'Subscribe for {price} to unlock this emote.', emote_lock) :
t.i18n.t('emote-menu.emote-up', 'Upgrade your sub to {price} to unlock this emote.', emote_lock) if ( emote_lock ) {
) : ''; if ( emote_lock.id === 'cheer' ) {
sellout = t.i18n.t('emote-menu.emote-cheer', 'Cheer an additional {bits_remaining,number} bit{bits_remaining,en_plural} to unlock this emote.', emote_lock);
} else if ( data.all_locked )
sellout = t.i18n.t('emote-menu.emote-sub', 'Subscribe for {price} to unlock this emote.', emote_lock);
else
sellout = t.i18n.t('emote-menu.emote-up', 'Upgrade your sub to {price} to unlock this emote.', emote_lock);
}
return this.renderEmote( return this.renderEmote(
emote, emote,
@ -698,14 +704,18 @@ export default class EmoteMenu extends Module {
if ( ! data.all_locked || ! data.locks ) if ( ! data.all_locked || ! data.locks )
return null; return null;
const lock = data.locks[this.state.unlocked]; const lock = data.locks[this.state.unlocked],
locks = Object.values(data.locks).filter(x => x.id !== 'cheer');
if ( ! locks.length )
return null;
return (<div class="tw-mg-1 tw-border-t tw-pd-t-1 tw-mg-b-0"> return (<div class="tw-mg-1 tw-border-t tw-pd-t-1 tw-mg-b-0">
{lock ? {lock ?
t.i18n.t('emote-menu.sub-unlock', 'Subscribe for {price} to unlock {count,number} emote{count,en_plural}', {price: lock.price, count: lock.emotes.size}) : t.i18n.t('emote-menu.sub-unlock', 'Subscribe for {price} to unlock {count,number} emote{count,en_plural}', {price: lock.price, count: lock.emotes.size}) :
t.i18n.t('emote-menu.sub-basic', 'Subscribe to unlock some emotes')} t.i18n.t('emote-menu.sub-basic', 'Subscribe to unlock some emotes')}
<div class="ffz--sub-buttons tw-mg-t-05"> <div class="ffz--sub-buttons tw-mg-t-05">
{Object.values(data.locks).map(lock => (<a {locks.map(lock => (<a
key={lock.price} key={lock.price}
class="tw-button tw-border-radius-none" class="tw-button tw-border-radius-none"
href={lock.url} href={lock.url}
@ -1282,9 +1292,15 @@ export default class EmoteMenu extends Module {
if ( a.locked && ! b.locked ) return 1; if ( a.locked && ! b.locked ) return 1;
if ( sort_tiers || a.locked || b.locked ) { if ( sort_tiers || a.locked || b.locked ) {
if ( COLLATOR ) {
const result = COLLATOR.compare(a.set_id, b.set_id);
if ( result != 0 )
return result;
} else {
if ( a.set_id < b.set_id ) return -1; if ( a.set_id < b.set_id ) return -1;
if ( a.set_id > b.set_id ) return 1; if ( a.set_id > b.set_id ) return 1;
} }
}
return sorter(a,b); return sorter(a,b);
} }
@ -1494,9 +1510,10 @@ export default class EmoteMenu extends Module {
// Now we handle the current Channel's emotes. // Now we handle the current Channel's emotes.
const user = props.channel_data && props.channel_data.user, const user = props.channel_data && props.channel_data.user,
products = user && user.subscriptionProducts; products = user && user.subscriptionProducts,
bits = user?.cheer?.badgeTierEmotes;
if ( Array.isArray(products) ) { if ( Array.isArray(products) || Array.isArray(bits) ) {
const badge = t.badges.getTwitchBadge('subscriber', '0', user.id, user.login), const badge = t.badges.getTwitchBadge('subscriber', '0', user.id, user.login),
emotes = [], emotes = [],
locks = {}, locks = {},
@ -1506,13 +1523,14 @@ export default class EmoteMenu extends Module {
image: badge && badge.image1x, image: badge && badge.image1x,
image_set: badge && `${badge.image1x} 1x, ${badge.image2x} 2x, ${badge.image4x} 4x`, image_set: badge && `${badge.image1x} 1x, ${badge.image2x} 2x, ${badge.image4x} 4x`,
icon: 'twitch', icon: 'twitch',
title: t.i18n.t('emote-menu.sub-set', 'Subscriber Emotes'), title: t.i18n.t('emote-menu.main-set', 'Channel Emotes'),
source: t.i18n.t('emote-menu.twitch', 'Twitch'), source: t.i18n.t('emote-menu.twitch', 'Twitch'),
emotes, emotes,
locks, locks,
all_locked: true all_locked: true
}; };
if ( Array.isArray(products) ) {
for(const product of products) { for(const product of products) {
if ( ! product || ! Array.isArray(product.emotes) ) if ( ! product || ! Array.isArray(product.emotes) )
continue; continue;
@ -1579,6 +1597,53 @@ export default class EmoteMenu extends Module {
lock_set.add(id); lock_set.add(id);
} }
} }
}
if ( Array.isArray(bits) ) {
for(const emote of bits) {
if ( ! emote || ! emote.id || ! emote.bitsBadgeTierSummary )
continue;
const id = emote.id,
set_id = emote.setID,
summary = emote.bitsBadgeTierSummary,
locked = ! twitch_seen.has(id) && ! summary.self?.isUnlocked;
// If the emote isn't unlocked, store data about that in the
// section so we can show appropriate UI to let people know
// that the emote isn't unlocked.
if ( locked )
locks[set_id] = {
set_id,
id: 'cheer',
price: null,
bits: summary.threshold,
bits_remaining: summary.self?.numberOfBitsUntilUnlock ?? summary.threshold,
emotes: new Set([emote.id])
}
const base = `${TWITCH_EMOTE_BASE}${id}`,
is_fav = twitch_favorites.includes(id);
const em = {
provider: 'twitch',
id,
set_id,
name: emote.token,
locked,
src: `${base}/1.0`,
srcSet: `${base}/1.0 1x, ${base}/2.0 2x`,
favorite: is_fav
};
emotes.push(em);
if ( ! locked && is_fav )
favorites.push(em);
twitch_seen.add(id);
}
}
if ( emotes.length ) { if ( emotes.length ) {
emotes.sort(sort_emotes); emotes.sort(sort_emotes);

View file

@ -588,18 +588,23 @@ export default class Scroller extends Module {
f === 9 ? t.i18n.t('key.shift_mouse', 'Shift or Mouse') : f === 9 ? t.i18n.t('key.shift_mouse', 'Shift or Mouse') :
t.i18n.t('key.mouse', 'Mouse Movement'); t.i18n.t('key.mouse', 'Mouse Movement');
msg = t.i18n.t('chat.paused', '(Chat Paused Due to {reason})', {reason}); msg = t.i18n.t('chat.paused', 'Chat Paused Due to {reason}', {reason});
cls = 'ffz--freeze-indicator'; cls = 'ffz--freeze-indicator';
} else if ( this.state.isAutoScrolling ) } else if ( this.state.isAutoScrolling )
return null; return null;
else else
msg = t.i18n.t('chat.messages-below', 'More messages below.'); msg = t.i18n.t('chat.messages-below', 'Chat Paused Due to Scroll');
return createElement('div', { return createElement('div', {
className: `chat-list__list-footer tw-absolute tw-align-items-center tw-border-radius-medium tw-bottom-0 tw-flex tw-full-width tw-justify-content-center tw-pd-05 ${cls}`, className: `chat-list__list-footer tw-absolute tw-align-items-center tw-border-radius-medium tw-bottom-0 tw-flex tw-justify-content-center tw-mg-b-1 tw-pd-x-2 tw-pd-y-05 ${cls}`,
onClick: this.ffzFastResume onClick: this.ffzFastResume
}, createElement('div', null, msg)); }, createElement('div', null, msg));
/*return createElement('div', {
className: `chat-list__list-footer tw-absolute tw-align-items-center tw-border-radius-medium tw-bottom-0 tw-flex tw-full-width tw-justify-content-center tw-pd-05 ${cls}`,
onClick: this.ffzFastResume
}, createElement('div', null, msg));*/
} }
cls.prototype.smoothScrollBottom = function() { cls.prototype.smoothScrollBottom = function() {

View file

@ -494,7 +494,7 @@ export default class Fine extends Module {
if ( idx !== -1 ) if ( idx !== -1 )
this._waiting.splice(idx, 1); this._waiting.splice(idx, 1);
this.log.info(`Found class for "${w.name}" at depth ${d.depth}`); this.log.debug(`Found class for "${w.name}" at depth ${d.depth}`);
w._set(d.cls, d.instances); w._set(d.cls, d.instances);
} }
} }

View file

@ -85,7 +85,7 @@ export default class WebMunch extends Module {
webpackJsonpv3(chunk_ids, modules) { webpackJsonpv3(chunk_ids, modules) {
const names = chunk_ids.map(x => this._module_names[x] || x).join(', '); const names = chunk_ids.map(x => this._module_names[x] || x).join(', ');
this.log.info(`Twitch Chunk Loaded: ${chunk_ids} (${names})`); this.log.debug(`Twitch Chunk Loaded: ${chunk_ids} (${names})`);
this.log.debug(`Modules: ${Object.keys(modules)}`); this.log.debug(`Modules: ${Object.keys(modules)}`);
const res = this._original_loader.apply(window, arguments); // eslint-disable-line prefer-rest-params const res = this._original_loader.apply(window, arguments); // eslint-disable-line prefer-rest-params
@ -129,7 +129,7 @@ export default class WebMunch extends Module {
modules = data[1], modules = data[1],
names = Array.isArray(chunk_ids) && chunk_ids.map(x => this._module_names[x] || x).join(', '); names = Array.isArray(chunk_ids) && chunk_ids.map(x => this._module_names[x] || x).join(', ');
this.log.info(`Twitch Chunk Loaded: ${chunk_ids} (${names})`); this.log.debug(`Twitch Chunk Loaded: ${chunk_ids} (${names})`);
this.log.debug(`Modules: ${Object.keys(modules)}`); this.log.debug(`Modules: ${Object.keys(modules)}`);
if ( modules ) if ( modules )

View file

@ -109,5 +109,6 @@ export const EmoteTypes = make_enum(
'Unavailable', 'Unavailable',
'Subscription', 'Subscription',
'BitsTier', 'BitsTier',
'Global' 'Global',
'TwoFactor'
); );