1
0
Fork 0
mirror of https://github.com/FrankerFaceZ/FrankerFaceZ.git synced 2025-06-27 21:05:53 +00:00
* Added: Setting to make emotes twice as big in chat.
* Added: Setting for hiding Prediction banners in chat.
* Fixed: Current channel, category, and title not being detected correctly on the Stream Dashboard page.
* Fixed: Tweak the clip URL regex again to support more possible characters.
* Fixed: Cyclic dependency error with the CSS Tweaks module.
* Fixed: Cyclic dependencies in modules not being detected due to how the enable/load/disable/unload promises work.
* Changed: In Switchboard, try finding a route with no parameters first to minimize errors in the log.
* Changed: Attempt to detect Firefox with `InstallTrigger` for users that change their User-Agent.
This commit is contained in:
SirStendec 2021-02-15 17:48:30 -05:00
parent 9cecab8dd4
commit 01e7a95cd8
13 changed files with 194 additions and 107 deletions

View file

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

View file

@ -3,8 +3,8 @@
"name": "New Link Tokenization",
"description": "Update to Twitch's latest link regex. Experiment while this is checked for bugs.",
"groups": [
{"value": true, "weight": 50},
{"value": false, "weight": 50}
{"value": true, "weight": 100},
{"value": false, "weight": 0}
]
},
"api_load": {
@ -19,8 +19,8 @@
"name": "API-Based Link Lookups",
"description": "Use the new API to look up links instead of the socket cluster.",
"groups": [
{"value": true, "weight": 50},
{"value": false, "weight": 50}
{"value": true, "weight": 30},
{"value": false, "weight": 70}
]
}
}

View file

@ -84,6 +84,16 @@ export default class Emotes extends Module {
this._set_refs = {};
this._set_timers = {};
this.settings.add('chat.emotes.2x', {
default: false,
ui: {
path: 'Chat > Appearance >> Emotes',
title: 'Larger Emotes',
description: 'This setting will make emotes appear twice as large in chat. It\'s good for use with larger fonts or just if you really like emotes.',
component: 'setting-check-box'
}
});
this.settings.add('chat.fix-bad-emotes', {
default: true,
ui: {
@ -721,6 +731,14 @@ export default class Emotes extends Module {
if ( emote.urls[4] )
emote.srcSet += `, ${emote.urls[4]} 4x`;
if ( emote.urls[2] ) {
emote.can_big = true;
emote.src2 = emote.urls[2];
emote.srcSet2 = `${emote.urls[2]} 1x`;
if ( emote.urls[4] )
emote.srcSet2 += `, ${emote.urls[4]} 2x`;
}
emote.token = {
type: 'emote',
id: emote.id,
@ -728,8 +746,12 @@ export default class Emotes extends Module {
provider: 'ffz',
src: emote.urls[1],
srcSet: emote.srcSet,
can_big: !! emote.urls[2],
src2: emote.src2,
srcSet2: emote.srcSet2,
text: emote.hidden ? '???' : emote.name,
length: emote.name.length
length: emote.name.length,
height: emote.height
};
if ( has(MODIFIERS, emote.id) )

View file

@ -6,8 +6,8 @@
//const CLIP_URL = /^(?:https?:\/\/)?clips\.twitch\.tv\/(\w+)(?:\/)?(\w+)?(?:\/edit)?/;
//const NEW_CLIP_URL = /^(?:https?:\/\/)?(?:www\.)?twitch\.tv\/\w+\/clip\/(\w+)/;
const CLIP_URL = /^(?:https?:\/\/)?clips\.twitch\.tv\/([a-z0-9-]+)(?:\/)?(\w+)?(?:\/edit)?/i;
const NEW_CLIP_URL = /^(?:https?:\/\/)?(?:www\.)?twitch\.tv\/\w+\/clip\/([a-z0-9-]+)/i;
const CLIP_URL = /^(?:https?:\/\/)?clips\.twitch\.tv\/([a-z0-9-_=]+)(?:\/)?(\w+)?(?:\/edit)?/i;
const NEW_CLIP_URL = /^(?:https?:\/\/)?(?:(?:www|m)\.)?twitch\.tv\/\w+\/clip\/([a-z0-9-_=]+)/i;
const VIDEO_URL = /^(?:https?:\/\/)?(?:www\.)?twitch\.tv\/(?:\w+\/v|videos)\/(\w+)/;
const USER_URL = /^(?:https?:\/\/)?(?:www\.)?twitch\.tv\/([^/]+)$/;

View file

@ -12,6 +12,7 @@ import {CATEGORIES} from './emoji';
const EMOTE_CLASS = 'chat-image chat-line__message--emote',
WHITESPACE = /^\s*$/,
LINK_REGEX = /([^\w@#%\-+=:~])?((?:(https?:\/\/)?(?:[\w@#%\-+=:~]+\.)+[a-z]{2,6}(?:\/[\w./@#%&()\-+=:?~]*)?))([^\w./@#%&()\-+=:?~]|\s|$)/g,
NEW_LINK_REGEX = /(?:(https?:\/\/)?((?:[\w#%\-+=:~]+\.)+[a-z]{2,10}(?:\/[\w./#%&@()\-+=:?~]*)?))/g,
//MENTION_REGEX = /([^\w@#%\-+=:~])?(@([^\u0000-\u007F]+|\w+)+)([^\w./@#%&()\-+=:?~]|\s|$)/g; // eslint-disable-line no-control-regex
@ -1057,9 +1058,10 @@ const render_emote = (token, createElement, wrapped) => {
emote = createElement('img', {
class: `${EMOTE_CLASS} ffz-tooltip${token.provider === 'ffz' ? ' ffz-emote' : token.provider === 'emoji' ? ' ffz-emoji' : ''}`,
attrs: {
src: token.src,
srcSet: token.srcSet,
src: token.big && token.src2 || token.src,
srcSet: token.big && token.srcSet2 || token.srcSet,
alt: token.text,
height: (token.big && ! token.can_big && token.height) ? `${token.height * 2}px` : undefined,
'data-tooltip-type': 'emote',
'data-provider': token.provider,
'data-id': token.id,
@ -1111,8 +1113,9 @@ export const AddonEmotes = {
const mods = token.modifiers || [], ml = mods.length,
emote = (<img
class={`${EMOTE_CLASS} ffz--pointer-events ffz-tooltip${token.provider === 'ffz' ? ' ffz-emote' : token.provider === 'emoji' ? ' ffz-emoji' : ''}`}
src={token.src}
srcSet={token.srcSet}
src={token.big && token.src2 || token.src}
srcSet={token.big && token.srcSet2 || token.srcSet}
height={(token.big && ! token.can_big && token.height) ? `${token.height * 2}px` : undefined}
alt={token.text}
data-tooltip-type="emote"
data-provider={token.provider}
@ -1291,24 +1294,28 @@ export const AddonEmotes = {
return tokens;
const emotes = this.emotes.getEmotes(
msg.user.id,
msg.user.login,
msg.roomID,
msg.roomLogin
),
out = [];
msg.user.id,
msg.user.login,
msg.roomID,
msg.roomLogin
);
if ( ! emotes )
return tokens;
const big = this.context.get('chat.emotes.2x'),
out = [];
let last_token, emote;
for(const token of tokens) {
if ( ! token )
continue;
if ( token.type !== 'text' ) {
if ( token.type === 'emote' && ! token.modifiers )
token.modifiers = [];
if ( token.type === 'emote' ) {
if ( ! token.modifiers )
token.modifiers = [];
}
out.push(token);
last_token = token;
@ -1323,8 +1330,14 @@ export const AddonEmotes = {
// Is this emote a modifier?
if ( emote.modifier && last_token && last_token.modifiers && (!text.length || (text.length === 1 && text[0] === '')) ) {
if ( last_token.modifiers.indexOf(emote.token) === -1 )
last_token.modifiers.push(emote.token);
if ( last_token.modifiers.indexOf(emote.token) === -1 ) {
if ( big )
last_token.modifiers.push(Object.assign({
big
}, emote.token));
else
last_token.modifiers.push(emote.token);
}
continue;
}
@ -1339,7 +1352,10 @@ export const AddonEmotes = {
text = [];
}
const t = Object.assign({modifiers: []}, emote.token);
const t = Object.assign({
modifiers: [],
big
}, emote.token);
out.push(t);
last_token = t;
@ -1349,8 +1365,10 @@ export const AddonEmotes = {
text.push(segment);
}
if ( text.length > 1 || (text.length === 1 && text[0] !== '') )
out.push({type: 'text', text: text.join(' ')});
if ( text.length > 1 || (text.length === 1 && text[0] !== '') ) {
const t = {type: 'text', text: text.join(' ')};
out.push(t);
}
}
return out;
@ -1445,6 +1463,8 @@ export const TwitchEmotes = {
return tokens;
const data = msg.ffz_emotes,
big = this.context.get('chat.emotes.2x'),
use_replacements = this.context.get('chat.fix-bad-emotes'),
emotes = [];
for(const emote_id in data)
@ -1516,15 +1536,21 @@ export const TwitchEmotes = {
});
let src, srcSet;
let src2, srcSet2;
const replacement = REPLACEMENTS[e_id];
if ( replacement && this.context.get('chat.fix-bad-emotes') ) {
if ( replacement && use_replacements ) {
src = `${REPLACEMENT_BASE}${replacement}`;
srcSet = '';
} else {
src = `${TWITCH_EMOTE_BASE}${e_id}/1.0`;
srcSet = `${TWITCH_EMOTE_BASE}${e_id}/1.0 1x, ${TWITCH_EMOTE_BASE}${e_id}/2.0 2x`;
if ( big ) {
src2 = `${TWITCH_EMOTE_BASE}${e_id}/2.0`;
srcSet2 = `${TWITCH_EMOTE_BASE}${e_id}/2.0 1x, ${TWITCH_EMOTE_BASE}${e_id}/3.0 2x`;
}
}
out.push({
@ -1533,6 +1559,9 @@ export const TwitchEmotes = {
provider: 'twitch',
src,
srcSet,
src2,
srcSet2,
big,
text: text.slice(e_start - t_start, e_end - t_start).join(''),
modifiers: []
});

View file

@ -33,7 +33,7 @@ export default class Channel extends Module {
this.settings.add('channel.panel-tips', {
default: true,
default: false,
ui: {
path: 'Channel > Behavior >> Panels',
title: 'Display rich tool-tips for links in channel panels.',

View file

@ -354,6 +354,15 @@ export default class ChatHook extends Module {
}
});
this.settings.add('chat.banners.prediction', {
default: true,
ui: {
path: 'Chat > Appearance >> Community',
title: 'Allow Predictions to be displayed in chat.',
component: 'setting-check-box'
}
});
this.settings.add('chat.community-chest.show', {
default: true,
ui: {
@ -773,6 +782,7 @@ export default class ChatHook extends Module {
this.chat.context.on('changed:chat.banners.hype-train', this.cleanHighlights, this);
this.chat.context.on('changed:chat.subs.gift-banner', this.cleanHighlights, this);
this.chat.context.on('changed:chat.banners.polls', this.cleanHighlights, this);
this.chat.context.on('changed:chat.banners.prediction', this.cleanHighlights, this);
this.chat.context.on('changed:chat.subs.gift-banner', () => this.GiftBanner.forceUpdate(), this);
this.chat.context.on('changed:chat.width', this.updateChatCSS, this);
@ -1258,6 +1268,7 @@ export default class ChatHook extends Module {
'community_sub_gift': this.chat.context.get('chat.subs.gift-banner'),
'megacheer': this.chat.context.get('chat.bits.show'),
'hype_train': this.chat.context.get('chat.banners.hype-train'),
'prediction': this.chat.context.get('chat.banners.prediction'),
'poll': this.chat.context.get('chat.banners.polls')
};

View file

@ -59,6 +59,7 @@ export default class ChatLine extends Module {
this.on('chat:update-lines', this.updateLines, this);
this.on('i18n:update', this.updateLines, this);
this.chat.context.on('changed:chat.emotes.2x', this.updateLines, this);
this.chat.context.on('changed:chat.emoji.style', this.updateLines, this);
this.chat.context.on('changed:chat.bits.stack', this.updateLines, this);
this.chat.context.on('changed:chat.badges.style', this.updateLines, this);

View file

@ -53,8 +53,6 @@ export default class CSSTweaks extends Module {
this.should_enable = true;
this.inject('settings');
this.inject('site.chat');
this.inject('site.theme');
this.style = new ManagedStyle;
this.chunks = {};

View file

@ -5,7 +5,7 @@
// ============================================================================
import Module from 'utilities/module';
import { get } from 'utilities/object';
import { get, has } from 'utilities/object';
import Twilight from 'site';
@ -19,83 +19,69 @@ export default class Dashboard extends Module {
this.inject('site.fine');
this.inject('site.channel');
this.HostBar = this.fine.define(
'sunlight-host-bar',
n => n.props && n.props.channel && n.props.hostedChannel !== undefined,
this.SunlightBroadcast = this.fine.define(
'sunlight-bcast',
n => n.getGame && n.getTitle && n.props?.data,
Twilight.SUNLIGHT_ROUTES
)
);
this.Dashboard = this.fine.define(
'sunlight-dash',
n => n.getIsChannelEditor && n.getIsChannelModerator && n.getIsAdsEnabled && n.getIsSquadStreamsEnabled,
this.SunlightManager = this.fine.define(
'sunlight-manager',
n => n.props?.channelID && n.handleChange && has(n, 'hasVisitedStreamManager'),
Twilight.SUNLIGHT_ROUTES
);
}
onEnable() {
this.Dashboard.on('mount', this.onDashUpdate, this);
this.Dashboard.on('update', this.onDashUpdate, this);
this.Dashboard.on('unmount', this.onDashUnmount, this);
this.HostBar.on('mount', this.onHostBarUpdate, this);
this.HostBar.on('update', this.onHostBarUpdate, this);
this.HostBar.on('unmount', this.onHostBarUnmount, this);
this.Dashboard.ready((cls, instances) => {
this.SunlightManager.on('mount', this.updateSunlight, this);
this.SunlightManager.on('update', this.updateSunlight, this);
this.SunlightManager.on('unmount', this.removeSunlight, this);
this.SunlightManager.ready((cls, instances) => {
for(const inst of instances)
this.onDashUpdate(inst);
this.updateSunlight(inst);
});
this.HostBar.ready((cls, instances) => {
this.SunlightBroadcast.on('mount', this.updateBroadcast, this);
this.SunlightBroadcast.on('update', this.updateBroadcast, this);
this.SunlightBroadcast.on('unmount', this.removeBroadcast, this);
this.SunlightBroadcast.ready((cls, instances) => {
for(const inst of instances)
this.onHostBarUpdate(inst);
this.updateBroadcast(inst);
});
}
onDashUpdate(inst) {
updateSunlight(inst) {
this.settings.updateContext({
channel: get('props.channelLogin', inst),
channelID: get('props.channelID', inst)
channelID: get('props.channelID', inst),
hosting: !! inst.props?.hostedChannel?.id
});
}
onDashUnmount() {
removeSunlight() {
this.settings.updateContext({
channel: null,
channelID: null
});
}
onHostBarUpdate(inst) {
const channel = inst.props?.channel,
source = channel?.stream || channel?.broadcastSettings;
const game = source?.game,
title = source?.title || null,
color = channel?.primaryColorHex || null;
this.channel.updateChannelColor(color);
this.settings.updateContext({
/*channel: channel?.login,
channelID: channel?.id,*/
category: game?.name,
categoryID: game?.id,
title,
channelColor: color,
hosting: !! inst.props?.hostedChannel
});
}
onHostBarUnmount() {
this.settings.updateContext({
/*channel: null,
channelID: null,*/
channelColor: null,
category: null,
categoryID: null,
title: null,
channelID: null,
hosting: false
});
}
updateBroadcast(inst) {
const data = inst.props?.data?.user?.broadcastSettings,
game = data?.game;
this.settings.updateContext({
category: game?.name,
categoryID: game?.id,
title: data?.title
});
}
removeBroadcast() {
this.settings.updateContext({
category: null,
categoryID: null,
title: null
});
}
}

View file

@ -66,10 +66,18 @@ export default class Switchboard extends Module {
this.log.info(`Found Route and Switch with ${da_switch.props.children.length} routes.`);
const location = router.props.location.pathname;
if ( ! this.loadRoute(da_switch, location, false) )
this.loadRoute(da_switch, location, true);
}
loadRoute(da_switch, location, with_params) {
for(const route of da_switch.props.children) {
if ( ! route.props || ! route.props.component )
continue;
if ( with_params !== null && with_params !== route.props.path.includes(':') )
continue;
try {
const reg = pathToRegexp(route.props.path);
if ( ! reg.exec || reg.exec(location) )
@ -124,7 +132,9 @@ export default class Switchboard extends Module {
}
}
break;
return true;
}
return false;
}
}

View file

@ -106,7 +106,7 @@ export const WS_CLUSTERS = {
export const IS_OSX = navigator.platform ? navigator.platform.indexOf('Mac') !== -1 : /OS X/.test(navigator.userAgent);
export const IS_WIN = navigator.platform ? navigator.platform.indexOf('Win') !== -1 : /Windows/.test(navigator.userAgent);
export const IS_WEBKIT = navigator.userAgent.indexOf('AppleWebKit/') !== -1 && navigator.userAgent.indexOf('Edge/') === -1;
export const IS_FIREFOX = navigator.userAgent.indexOf('Firefox/') !== -1;
export const IS_FIREFOX = (navigator.userAgent.indexOf('Firefox/') !== -1) || (window.InstallTrigger !== undefined);
export const WEBKIT_CSS = IS_WEBKIT ? '-webkit-' : '';

View file

@ -116,6 +116,17 @@ export class Module extends EventEmitter {
const path = this.__path || this.name,
state = this.__load_state;
if ( chain.includes(this) )
return Promise.reject(new CyclicDependencyError(`cyclic load requirements when loading ${initial}`, [...chain, this]));
else if ( this.load_requires )
for(const name of this.load_requires) {
const module = this.resolve(name);
if ( module && chain.includes(module) )
return Promise.reject(new CyclicDependencyError(`cyclic load requirements when loading ${initial}`, [...chain, this, module]));
}
chain.push(this);
if ( state === State.LOADING )
return this.__load_promise;
@ -125,11 +136,6 @@ export class Module extends EventEmitter {
else if ( state === State.UNLOADING )
return Promise.reject(new ModuleError(`attempted to load module ${path} while module is being unloaded`));
else if ( chain.includes(this) )
return Promise.reject(new CyclicDependencyError(`cyclic load requirements when loading ${initial}`, chain));
chain.push(this);
this.__time('load-start');
this.__load_state = State.LOADING;
return this.__load_promise = (async () => {
@ -170,6 +176,17 @@ export class Module extends EventEmitter {
const path = this.__path || this.name,
state = this.__load_state;
if ( chain.includes(this) )
return Promise.reject(new CyclicDependencyError(`cyclic load requirements when unloading ${initial}`, [...chain, this]));
else if ( this.load_dependents )
for(const dep of this.load_dependents) {
const module = this.resolve(dep);
if ( module && chain.includes(module) )
return Promise.reject(new CyclicDependencyError(`cyclic load requirements when unloading ${initial}`, [...chain, this, module]));
}
chain.push(this);
if ( state === State.UNLOADING )
return this.__load_promise;
@ -182,10 +199,6 @@ export class Module extends EventEmitter {
else if ( state === State.LOADING )
return Promise.reject(new ModuleError(`attempted to unload module ${path} while module is being loaded`));
else if ( chain.includes(this) )
return Promise.reject(new CyclicDependencyError(`cyclic load requirements when unloading ${initial}`, chain));
chain.push(this);
this.__time('unload-start');
this.__load_state = State.UNLOADING;
return this.__load_promise = (async () => {
@ -197,7 +210,8 @@ export class Module extends EventEmitter {
for(const name of this.load_dependents) {
const module = this.resolve(name);
if ( ! module )
throw new ModuleError(`cannot find depending module ${name} when unloading ${path}`);
//throw new ModuleError(`cannot find depending module ${name} when unloading ${path}`);
continue;
promises.push(module.__unload([], initial, Array.from(chain)));
}
@ -227,6 +241,17 @@ export class Module extends EventEmitter {
const path = this.__path || this.name,
state = this.__state;
if ( chain.includes(this) )
return Promise.reject(new CyclicDependencyError(`cyclic requirements when enabling ${initial}`, [...chain, this]));
else if ( this.requires )
for(const name of this.requires) {
const module = this.resolve(name);
if ( module && chain.includes(module) )
return Promise.reject(new CyclicDependencyError(`cyclic requirements when enabling ${initial}`, [...chain, this, module]));
}
chain.push(this);
if ( state === State.ENABLING )
return this.__state_promise;
@ -236,10 +261,6 @@ export class Module extends EventEmitter {
else if ( state === State.DISABLING )
return Promise.reject(new ModuleError(`attempted to enable module ${path} while module is being disabled`));
else if ( chain.includes(this) )
return Promise.reject(new CyclicDependencyError(`cyclic requirements when enabling ${initial}`, chain));
chain.push(this);
this.__time('enable-start');
this.__state = State.ENABLING;
return this.__state_promise = (async () => {
@ -290,6 +311,17 @@ export class Module extends EventEmitter {
const path = this.__path || this.name,
state = this.__state;
if ( chain.includes(this) )
return Promise.reject(new CyclicDependencyError(`cyclic requirements when disabling ${initial}`, [...chain, this]));
else if ( this.dependents )
for(const dep of this.dependents) {
const module = this.resolve(dep);
if ( module && chain.includes(module) )
return Promise.reject(new CyclicDependencyError(`cyclic requirements when disabling ${initial}`, [...chain, this, dep]));
}
chain.push(this);
if ( state === State.DISABLING )
return this.__state_promise;
@ -302,10 +334,6 @@ export class Module extends EventEmitter {
else if ( state === State.ENABLING )
return Promise.reject(new ModuleError(`attempted to disable module ${path} but module is being enabled`));
else if ( chain.includes(this) )
return Promise.reject(new CyclicDependencyError(`cyclic requirements when disabling ${initial}`, chain));
chain.push(this);
this.__time('disable-start');
this.__state = State.DISABLING;
return this.__state_promise = (async () => {
@ -319,7 +347,9 @@ export class Module extends EventEmitter {
for(const name of this.dependents) {
const module = this.resolve(name);
if ( ! module )
throw new ModuleError(`cannot find depending module ${name} when disabling ${path}`);
// Assume a non-existent module isn't enabled.
//throw new ModuleError(`cannot find depending module ${name} when disabling ${path}`);
continue;
promises.push(module.__disable([], initial, Array.from(chain)));
}
@ -665,7 +695,7 @@ export class ModuleError extends Error { }
export class CyclicDependencyError extends ModuleError {
constructor(message, modules) {
super(message);
super(`${message} (${modules.map(x => x.path).join(' => ')})`);
this.modules = modules;
}
}