1
0
Fork 0
mirror of https://github.com/FrankerFaceZ/FrankerFaceZ.git synced 2025-06-28 15:27:43 +00:00
* 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:
SirStendec 2020-11-15 17:33:55 -05:00
parent 32859318b2
commit c97928fbb7
8 changed files with 164 additions and 44 deletions

View file

@ -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": {

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": 100}, {"value": true, "weight": 0},
{"value": false, "weight": 0} {"value": false, "weight": 100}
] ]
}, },
"api_links": { "api_links": {

View file

@ -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) {

View file

@ -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');

View file

@ -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;

View file

@ -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) )

View file

@ -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})
}); });

View file

@ -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);