mirror of
https://github.com/FrankerFaceZ/FrankerFaceZ.git
synced 2025-07-02 17:18:31 +00:00
4.19.6
* 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:
parent
99eee7396d
commit
30b9a15c0d
12 changed files with 191 additions and 63 deletions
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "frankerfacez",
|
||||
"author": "Dan Salvato LLC",
|
||||
"version": "4.19.5",
|
||||
"version": "4.19.6",
|
||||
"description": "FrankerFaceZ is a Twitch enhancement suite.",
|
||||
"license": "Apache-2.0",
|
||||
"scripts": {
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
"name": "New API Stress Testing",
|
||||
"description": "Send duplicate requests to the new API server for load testing.",
|
||||
"groups": [
|
||||
{"value": true, "weight": 75},
|
||||
{"value": false, "weight": 25}
|
||||
{"value": true, "weight": 50},
|
||||
{"value": false, "weight": 50}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -963,6 +963,9 @@ function determineEmoteType(emote) {
|
|||
if ( emote.setID == 300238151 )
|
||||
return EmoteTypes.ChannelPoints;
|
||||
|
||||
if ( emote.setID == 300374282 )
|
||||
return EmoteTypes.TwoFactor;
|
||||
|
||||
const id = parseInt(emote.setID, 10);
|
||||
if ( ! isNaN(id) && isFinite(id) && id >= 5e8 )
|
||||
return EmoteTypes.BitsTier;
|
||||
|
@ -983,6 +986,9 @@ function determineSetType(set) {
|
|||
if ( TWITCH_PRIME_SETS.includes(id) )
|
||||
return EmoteTypes.Prime;
|
||||
|
||||
if ( id == 300374282 )
|
||||
return EmoteTypes.TwoFactor;
|
||||
|
||||
const owner = set.owner;
|
||||
if ( owner ) {
|
||||
if ( owner.id == 139075904 )
|
||||
|
|
|
@ -1059,6 +1059,9 @@ export const AddonEmotes = {
|
|||
} else if ( type === EmoteTypes.Prime || type === EmoteTypes.Turbo )
|
||||
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 )
|
||||
source = this.i18n.t('emote.limited', 'Limited-Time Only Emote');
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
:id="item.full_key"
|
||||
ref="control"
|
||||
:alpha="alpha"
|
||||
:open-up="openUp"
|
||||
:nullable="true"
|
||||
:value="color"
|
||||
@input="onInput"
|
||||
|
@ -73,6 +74,13 @@ export default {
|
|||
return this.value;
|
||||
},
|
||||
|
||||
openUp() {
|
||||
if ( this.item.openUp != null )
|
||||
return this.item.openUp;
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
alpha() {
|
||||
if ( this.item.alpha != null )
|
||||
return this.item.alpha;
|
||||
|
|
|
@ -56,7 +56,7 @@ export default class Channel extends Module {
|
|||
this.ChannelPage = this.fine.define(
|
||||
'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')),
|
||||
['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(
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
import Module from 'utilities/module';
|
||||
import {get} from 'utilities/object';
|
||||
import {createElement} from 'utilities/dom';
|
||||
|
||||
//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,
|
||||
['user']
|
||||
);
|
||||
|
||||
this.ModWidget = this.fine.define(
|
||||
'mod-widget',
|
||||
n => n.renderToolbar && n.getToolbarControls && n.childContext,
|
||||
['mod-view']
|
||||
);
|
||||
}
|
||||
|
||||
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('mount', 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) {
|
||||
const container = this.fine.getChildNode(inst),
|
||||
timestamp = container && container.querySelector('[data-test-selector="date"]');
|
||||
|
|
|
@ -637,11 +637,17 @@ export default class EmoteMenu extends Module {
|
|||
return;
|
||||
|
||||
const locked = emote.locked && (! lock || ! lock.emotes.has(emote.id)),
|
||||
emote_lock = locked && data.locks && data.locks[emote.set_id],
|
||||
sellout = emote_lock ? (data.all_locked ?
|
||||
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)
|
||||
) : '';
|
||||
emote_lock = locked && data.locks && data.locks[emote.set_id];
|
||||
let sellout = '';
|
||||
|
||||
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(
|
||||
emote,
|
||||
|
@ -698,14 +704,18 @@ export default class EmoteMenu extends Module {
|
|||
if ( ! data.all_locked || ! data.locks )
|
||||
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">
|
||||
{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-basic', 'Subscribe to unlock some emotes')}
|
||||
<div class="ffz--sub-buttons tw-mg-t-05">
|
||||
{Object.values(data.locks).map(lock => (<a
|
||||
{locks.map(lock => (<a
|
||||
key={lock.price}
|
||||
class="tw-button tw-border-radius-none"
|
||||
href={lock.url}
|
||||
|
@ -1282,9 +1292,15 @@ export default class EmoteMenu extends Module {
|
|||
if ( a.locked && ! b.locked ) return 1;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
return sorter(a,b);
|
||||
}
|
||||
|
@ -1494,9 +1510,10 @@ export default class EmoteMenu extends Module {
|
|||
// Now we handle the current Channel's emotes.
|
||||
|
||||
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),
|
||||
emotes = [],
|
||||
locks = {},
|
||||
|
@ -1506,13 +1523,14 @@ export default class EmoteMenu extends Module {
|
|||
image: badge && badge.image1x,
|
||||
image_set: badge && `${badge.image1x} 1x, ${badge.image2x} 2x, ${badge.image4x} 4x`,
|
||||
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'),
|
||||
emotes,
|
||||
locks,
|
||||
all_locked: true
|
||||
};
|
||||
|
||||
if ( Array.isArray(products) ) {
|
||||
for(const product of products) {
|
||||
if ( ! product || ! Array.isArray(product.emotes) )
|
||||
continue;
|
||||
|
@ -1579,6 +1597,53 @@ export default class EmoteMenu extends Module {
|
|||
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 ) {
|
||||
emotes.sort(sort_emotes);
|
||||
|
|
|
@ -588,18 +588,23 @@ export default class Scroller extends Module {
|
|||
f === 9 ? t.i18n.t('key.shift_mouse', 'Shift or Mouse') :
|
||||
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';
|
||||
|
||||
} else if ( this.state.isAutoScrolling )
|
||||
return null;
|
||||
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', {
|
||||
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
|
||||
}, 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() {
|
||||
|
|
|
@ -494,7 +494,7 @@ export default class Fine extends Module {
|
|||
if ( 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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -85,7 +85,7 @@ export default class WebMunch extends Module {
|
|||
|
||||
webpackJsonpv3(chunk_ids, modules) {
|
||||
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)}`);
|
||||
|
||||
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],
|
||||
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)}`);
|
||||
|
||||
if ( modules )
|
||||
|
|
|
@ -109,5 +109,6 @@ export const EmoteTypes = make_enum(
|
|||
'Unavailable',
|
||||
'Subscription',
|
||||
'BitsTier',
|
||||
'Global'
|
||||
'Global',
|
||||
'TwoFactor'
|
||||
);
|
Loading…
Add table
Add a link
Reference in a new issue