mirror of
https://github.com/FrankerFaceZ/FrankerFaceZ.git
synced 2025-06-28 23:37:41 +00:00
4.20.49
* Added: Override the "Identity" rendering in Twitch's chat settings menu to apply FFZ badge settings and name color correction. * Added: Insert a "FrankerFaceZ Badge" section within the "Identity" editor. Currently, this only directs users to the website to change their badge. * Fixed: Bugs with corrupt settings causing the emote menu not to function correctly for certain users. * Changed: Rename `Twitch Prime` to `Prime Gaming` in several strings. * Changed: Disable the API stress testing experiment ahead of the server maintenance tomorrow.
This commit is contained in:
parent
32859318b2
commit
c97928fbb7
8 changed files with 164 additions and 44 deletions
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "frankerfacez",
|
"name": "frankerfacez",
|
||||||
"author": "Dan Salvato LLC",
|
"author": "Dan Salvato LLC",
|
||||||
"version": "4.20.48",
|
"version": "4.20.49",
|
||||||
"description": "FrankerFaceZ is a Twitch enhancement suite.",
|
"description": "FrankerFaceZ is a Twitch enhancement suite.",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
|
@ -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": 100},
|
{"value": true, "weight": 0},
|
||||||
{"value": false, "weight": 0}
|
{"value": false, "weight": 100}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"api_links": {
|
"api_links": {
|
||||||
|
|
|
@ -228,7 +228,7 @@ export default class Emotes extends Module {
|
||||||
}
|
}
|
||||||
|
|
||||||
getHidden(source) {
|
getHidden(source) {
|
||||||
return this.settings.provider.get(`hidden-emotes.${source}`, []);
|
return this.settings.provider.get(`hidden-emotes.${source}`) || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
setHidden(source, list) {
|
setHidden(source, list) {
|
||||||
|
|
|
@ -1169,7 +1169,7 @@ 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', 'Prime Gaming');
|
||||||
|
|
||||||
else if ( type === EmoteTypes.TwoFactor )
|
else if ( type === EmoteTypes.TwoFactor )
|
||||||
source = this.i18n.t('emote.2fa', 'Twitch 2FA Emote');
|
source = this.i18n.t('emote.2fa', 'Twitch 2FA Emote');
|
||||||
|
|
|
@ -181,6 +181,8 @@ export default class Channel extends Module {
|
||||||
if ( inst )
|
if ( inst )
|
||||||
this.updatePanelTips(inst);
|
this.updatePanelTips(inst);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const el = this.fine.getChildNode(inst);
|
const el = this.fine.getChildNode(inst);
|
||||||
|
@ -337,6 +339,11 @@ export default class Channel extends Module {
|
||||||
isWatchParty: watching
|
isWatchParty: watching
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if ( ! el._ffz_cont || ! props?.channelID ) {
|
||||||
|
this.updateSubscription(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if ( el._ffz_links && props.channelLogin !== el._ffz_link_login ) {
|
if ( el._ffz_links && props.channelLogin !== el._ffz_link_login ) {
|
||||||
const login = el._ffz_link_login = props.channelLogin;
|
const login = el._ffz_link_login = props.channelLogin;
|
||||||
if ( login ) {
|
if ( login ) {
|
||||||
|
@ -369,11 +376,6 @@ export default class Channel extends Module {
|
||||||
el._ffz_links.innerHTML = '';
|
el._ffz_links.innerHTML = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( ! el._ffz_cont || ! props?.channelID ) {
|
|
||||||
this.updateSubscription(null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: See if we can read this data directly from Apollo's cache.
|
// TODO: See if we can read this data directly from Apollo's cache.
|
||||||
// Also, test how it works with videos and clips.
|
// Also, test how it works with videos and clips.
|
||||||
/*const raw_game = el.querySelector('a[data-a-target="stream-game-link"]')?.textContent;
|
/*const raw_game = el.querySelector('a[data-a-target="stream-game-link"]')?.textContent;
|
||||||
|
|
|
@ -675,7 +675,7 @@ export default class EmoteMenu extends Module {
|
||||||
if ( data.prime )
|
if ( data.prime )
|
||||||
calendar = {
|
calendar = {
|
||||||
icon: 'crown',
|
icon: 'crown',
|
||||||
message: t.i18n.t('emote-menu.sub-prime', 'This is your free sub with Twitch Prime.\nIt ends in {seconds,humantime}.', {seconds: ends})
|
message: t.i18n.t('emote-menu.sub-prime', 'This is your free sub with Prime Gaming.\nIt ends in {seconds,humantime}.', {seconds: ends})
|
||||||
}
|
}
|
||||||
else if ( data.gift )
|
else if ( data.gift )
|
||||||
calendar = {
|
calendar = {
|
||||||
|
@ -1263,7 +1263,7 @@ export default class EmoteMenu extends Module {
|
||||||
return out;
|
return out;
|
||||||
|
|
||||||
const filtering = input && input.length > 0 && input !== ':',
|
const filtering = input && input.length > 0 && input !== ':',
|
||||||
hidden_sets = storage.get('emote-menu.hidden-sets', []);
|
hidden_sets = storage.get('emote-menu.hidden-sets') || [];
|
||||||
|
|
||||||
for(const emote_set of sets) {
|
for(const emote_set of sets) {
|
||||||
if ( ! visibility_control && hidden_sets.includes(emote_set.key) )
|
if ( ! visibility_control && hidden_sets.includes(emote_set.key) )
|
||||||
|
|
|
@ -731,7 +731,7 @@ other {# messages were deleted by a moderator.}
|
||||||
className: 'tw-c-text-base tw-strong'
|
className: 'tw-c-text-base tw-strong'
|
||||||
}, user.displayName)),
|
}, user.displayName)),
|
||||||
plan: plan.prime ?
|
plan: plan.prime ?
|
||||||
t.i18n.t('chat.sub.twitch-prime', 'with Twitch Prime') :
|
t.i18n.t('chat.sub.twitch-prime', 'with Prime Gaming') :
|
||||||
t.i18n.t('chat.sub.plan', 'at Tier {tier}', {tier})
|
t.i18n.t('chat.sub.plan', 'at Tier {tier}', {tier})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -6,8 +6,7 @@
|
||||||
|
|
||||||
import Twilight from 'site';
|
import Twilight from 'site';
|
||||||
import Module from 'utilities/module';
|
import Module from 'utilities/module';
|
||||||
|
import {createElement} from 'utilities/dom';
|
||||||
import { has } from 'utilities/object';
|
|
||||||
|
|
||||||
export default class SettingsMenu extends Module {
|
export default class SettingsMenu extends Module {
|
||||||
constructor(...args) {
|
constructor(...args) {
|
||||||
|
@ -16,6 +15,7 @@ export default class SettingsMenu extends Module {
|
||||||
this.inject('settings');
|
this.inject('settings');
|
||||||
this.inject('i18n');
|
this.inject('i18n');
|
||||||
this.inject('chat');
|
this.inject('chat');
|
||||||
|
this.inject('chat.badges');
|
||||||
this.inject('site.fine');
|
this.inject('site.fine');
|
||||||
this.inject('site.web_munch');
|
this.inject('site.web_munch');
|
||||||
|
|
||||||
|
@ -24,12 +24,6 @@ export default class SettingsMenu extends Module {
|
||||||
n => n.renderUniversalOptions && n.onBadgesChanged,
|
n => n.renderUniversalOptions && n.onBadgesChanged,
|
||||||
Twilight.CHAT_ROUTES
|
Twilight.CHAT_ROUTES
|
||||||
);
|
);
|
||||||
|
|
||||||
/*this.ModSettingsMenu = this.fine.define(
|
|
||||||
'chat-mod-settings',
|
|
||||||
n => n.renderModerationSettingsLink && n.onChatClear,
|
|
||||||
Twilight.CHAT_ROUTES
|
|
||||||
);*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async onEnable() {
|
async onEnable() {
|
||||||
|
@ -171,35 +165,159 @@ export default class SettingsMenu extends Module {
|
||||||
this.SettingsMenu.forceUpdate();
|
this.SettingsMenu.forceUpdate();
|
||||||
});
|
});
|
||||||
|
|
||||||
/*this.ModSettingsMenu.ready(cls => {
|
this.SettingsMenu.on('update', this.updateSettingsMenu, this);
|
||||||
const old_render = cls.prototype.render;
|
|
||||||
|
|
||||||
cls.prototype.render = function() {
|
|
||||||
const out = old_render.call(this),
|
|
||||||
children = out?.props?.children?.[0]?.props?.children;
|
|
||||||
|
|
||||||
if ( Array.isArray(children) ) {
|
|
||||||
let i = children.length;
|
|
||||||
while(i--) {
|
|
||||||
const thing = children[i];
|
|
||||||
if ( thing && thing.props && has(thing.props, 'chatPauseSetting') ) {
|
|
||||||
children.splice(i, 1);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.ModSettingsMenu.forceUpdate();
|
|
||||||
})*/
|
|
||||||
|
|
||||||
this.SettingsMenu.on('unmount', inst => {
|
this.SettingsMenu.on('unmount', inst => {
|
||||||
inst.ffzSettingsClick = null;
|
inst.ffzSettingsClick = null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateSettingsMenu(inst) {
|
||||||
|
const el = this.fine.getChildNode(inst);
|
||||||
|
if ( ! el || ! document.contains(el) )
|
||||||
|
return;
|
||||||
|
|
||||||
|
let cont;
|
||||||
|
|
||||||
|
if ( inst.props?.editAppearance ) {
|
||||||
|
this.registerBadgePicker();
|
||||||
|
const name = el.querySelector('span[data-a-target="edit-display-name"]');
|
||||||
|
cont = name && name.closest('.tw-mg-t-1');
|
||||||
|
|
||||||
|
} else
|
||||||
|
cont = el.querySelector('.name-display > div:not([data-test-selector="edit-appearance-button"])');
|
||||||
|
|
||||||
|
if ( ! cont || ! el.contains(cont) )
|
||||||
|
return;
|
||||||
|
|
||||||
|
const user = inst.props?.data?.currentUser,
|
||||||
|
raw_badges = inst.props?.data?.user?.self?.displayBadges;
|
||||||
|
|
||||||
|
if ( ! user || ! user.login || ! Array.isArray(raw_badges) )
|
||||||
|
return;
|
||||||
|
|
||||||
|
const is_intl = user.login && user.displayName && user.displayName.trim().toLowerCase() !== user.login,
|
||||||
|
color = this.parent.colors.process(user.chatColor),
|
||||||
|
badges = {};
|
||||||
|
|
||||||
|
for(const child of cont.children) {
|
||||||
|
if ( ! child?.classList )
|
||||||
|
continue;
|
||||||
|
if ( child.classList.contains('ffz--editor-name') )
|
||||||
|
child.remove();
|
||||||
|
else
|
||||||
|
child.classList.add('tw-hide');
|
||||||
|
}
|
||||||
|
|
||||||
|
for(const badge of raw_badges) {
|
||||||
|
if ( badge?.setID && badge.version )
|
||||||
|
badges[badge.setID] = badge.version;
|
||||||
|
}
|
||||||
|
|
||||||
|
cont.appendChild(createElement('span', {className: 'ffz--editor-name'}, [
|
||||||
|
createElement('span', {
|
||||||
|
className: 'ffz--editor-badges',
|
||||||
|
'data-room-id': inst.props.channelID,
|
||||||
|
'data-room-login': inst.props.channelLogin
|
||||||
|
}, this.badges.render({
|
||||||
|
user,
|
||||||
|
badges,
|
||||||
|
roomID: inst.props.channelID,
|
||||||
|
roomLogin: inst.props.channelLogin
|
||||||
|
}, createElement, true)),
|
||||||
|
|
||||||
|
<span class="tw-strong notranslate" style={{color}}>
|
||||||
|
<span class="name-display__name">{user.displayName || user.login}</span>
|
||||||
|
{is_intl && <span class="intl-name"> ({user.login})</span>}
|
||||||
|
</span>
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
registerBadgePicker() {
|
||||||
|
if ( this.BadgePicker )
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.BadgePicker = this.fine.define(
|
||||||
|
'badge-picker',
|
||||||
|
n => n.onGlobalBadgeClicked && n.onGlobalBadgeKeyPress,
|
||||||
|
Twilight.CHAT_ROUTES
|
||||||
|
);
|
||||||
|
|
||||||
|
this.BadgePicker.on('mount', this.updateBadgePicker, this);
|
||||||
|
this.BadgePicker.on('update', this.updateBadgePicker, this);
|
||||||
|
|
||||||
|
this.BadgePicker.ready((cls, instances) => {
|
||||||
|
for(const inst of instances)
|
||||||
|
this.updateBadgePicker(inst);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateBadgePicker(inst) {
|
||||||
|
const el = this.fine.getChildNode(inst);
|
||||||
|
if ( ! el ) {
|
||||||
|
// This element doesn't populate right away. React is weird.
|
||||||
|
if ( inst.props?.data?.loading === false && ! inst._ffz_try_again )
|
||||||
|
inst._ffz_try_again = requestAnimationFrame(() =>
|
||||||
|
this.updateBadgePicker(inst));
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cont = el.querySelector('.tw-flex-column');
|
||||||
|
if ( ! cont )
|
||||||
|
return;
|
||||||
|
|
||||||
|
const badges = this.badges.getBadges(inst.props.userID, inst.props.userLogin);
|
||||||
|
let badge;
|
||||||
|
|
||||||
|
for(const b of badges) {
|
||||||
|
const bd = this.badges.badges[b?.id];
|
||||||
|
if ( bd && ! bd.addon )
|
||||||
|
badge = bd;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ffz = cont.querySelector('.ffz--badge-selector');
|
||||||
|
if ( ffz ) {
|
||||||
|
if ( ! badge )
|
||||||
|
ffz.remove();
|
||||||
|
return;
|
||||||
|
|
||||||
|
} else if ( ! badge )
|
||||||
|
return;
|
||||||
|
|
||||||
|
cont.appendChild(<div class="ffz--badge-selector">
|
||||||
|
<p class="tw-pd-x-05">
|
||||||
|
{this.i18n.tList('chat.ffz-badge.about', '{title}: This badge appears globally for users with FrankerFaceZ.', {
|
||||||
|
title: <span class="tw-strong">{this.i18n.t('chat.ffz-badge.title', 'FrankerFaceZ Badge')}</span>
|
||||||
|
})}{' '}
|
||||||
|
{this.i18n.tList('chat.ffz-badge.site', 'Please visit the {website} to change this badge.', {
|
||||||
|
website: (<a href="https://www.frankerfacez.com/donate" class="tw-link" rel="noopener noreferrer" target="_blank">
|
||||||
|
{this.i18n.t('chat.ffz-badge.site-link', 'FrankerFaceZ website')}
|
||||||
|
</a>)
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
<div role="radiogroup" class="tw-align-items-center tw-flex tw-flex-wrap tw-mg-b-05 tw-mg-t-05 tw-pd-x-05">
|
||||||
|
<div class="tw-mg-r-1 tw-mg-y-05">
|
||||||
|
<div class="tw-inline-flex">
|
||||||
|
<div class="edit-appearance__badge-chooser edit-appearance__badge-chooser--selected tw-border-radius-small">
|
||||||
|
<img
|
||||||
|
alt={badge.title}
|
||||||
|
src={badge.urls[2]}
|
||||||
|
srcset={`${badge.urls[2]} 1x, ${badge.urls[4]} 2x`}
|
||||||
|
style={{backgroundColor: badge.color || 'transparent'}}
|
||||||
|
role="radio"
|
||||||
|
aria-checked="true"
|
||||||
|
class="tw-full-height tw-full-width"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
closeMenu(inst) {
|
closeMenu(inst) {
|
||||||
const super_parent = this.fine.searchParent(inst, n => n.setChatInputRef && n.setAutocompleteInputRef, 100),
|
const super_parent = this.fine.searchParent(inst, n => n.setChatInputRef && n.setAutocompleteInputRef, 100),
|
||||||
parent = super_parent && this.fine.searchTree(super_parent, n => n.props && n.props.isSettingsOpen && n.onClickSettings);
|
parent = super_parent && this.fine.searchTree(super_parent, n => n.props && n.props.isSettingsOpen && n.onClickSettings);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue