-
+
diff --git a/src/sites/twitch-twilight/modules/css_tweaks/index.js b/src/sites/twitch-twilight/modules/css_tweaks/index.js
index 0d29144c..d0b83dc2 100644
--- a/src/sites/twitch-twilight/modules/css_tweaks/index.js
+++ b/src/sites/twitch-twilight/modules/css_tweaks/index.js
@@ -40,7 +40,7 @@ const CLASSES = {
'dir-live-ind': '.live-channel-card[data-ffz-type="live"] .tw-channel-status-text-indicator, article[data-ffz-type="live"] .tw-channel-status-text-indicator',
'profile-hover': '.preview-card .tw-relative:hover .ffz-channel-avatar',
'not-live-bar': 'div[data-test-selector="non-live-video-banner-layout"]',
- 'channel-live-ind': '.channel-header__user .tw-channel-status-text-indicator,.channel-info-content .user-avatar-animated__live',
+ 'channel-live-ind': '.channel-header__user .tw-channel-status-text-indicator,.channel-info-content .tw-halo__indicator',
'celebration': 'body .celebration__overlay',
'mod-view': '.chat-input__buttons-container .tw-core-button[href*="/moderator"]'
};
diff --git a/src/sites/twitch-twilight/modules/css_tweaks/styles/square-avatars.scss b/src/sites/twitch-twilight/modules/css_tweaks/styles/square-avatars.scss
index 35fc2ad1..c0805b24 100644
--- a/src/sites/twitch-twilight/modules/css_tweaks/styles/square-avatars.scss
+++ b/src/sites/twitch-twilight/modules/css_tweaks/styles/square-avatars.scss
@@ -6,7 +6,9 @@
}
.user-avatar-card__halo,
-.player-streaminfo__picture img[src] {
+.player-streaminfo__picture img[src],
+.channel-info-content .tw-halo,
+.channel-info-content .tw-halo:before {
border-radius: 0 !important;
}
diff --git a/src/sites/twitch-twilight/switchboard.js b/src/sites/twitch-twilight/switchboard.js
index b08c0538..aba0b0af 100644
--- a/src/sites/twitch-twilight/switchboard.js
+++ b/src/sites/twitch-twilight/switchboard.js
@@ -7,6 +7,7 @@
import Module from 'utilities/module';
import pathToRegexp from 'path-to-regexp';
+import { sleep } from 'src/utilities/object';
export default class Switchboard extends Module {
@@ -21,7 +22,7 @@ export default class Switchboard extends Module {
}
- awaitRouter() {
+ awaitRouter(count = 0) {
const router = this.fine.searchTree(null,
n => (n.logger && n.logger.category === 'default-root-router') ||
(n.onHistoryChange && n.reportInteractive) ||
@@ -31,7 +32,51 @@ export default class Switchboard extends Module {
if ( router )
return Promise.resolve(router);
- return new Promise(r => setTimeout(r, 50)).then(() => this.awaitRouter());
+ if ( count > 50 )
+ return Promise.resolve(null);
+
+ return sleep(50).then(() => this.awaitRouter(count + 1));
+ }
+
+
+ awaitRoutes(count = 0) {
+ const routes = this.fine.searchTree(null,
+ n => n.props?.component && n.props.path,
+ 100, 0, false, true);
+
+ if ( routes?.size )
+ return Promise.resolve(routes);
+
+ if ( count > 50 )
+ return Promise.resolve(null);
+
+ return sleep(50).then(() => this.awaitRoutes(count + 1));
+ }
+
+
+ getSwitches(routes) {
+ const switches = new Set;
+ for(const route of routes) {
+ const switchy = this.fine.searchParent(route, n => n.props?.children);
+ if ( switchy )
+ switches.add(switchy);
+ }
+
+ return switches;
+ }
+
+
+ getPossibleRoutes(switches) { // eslint-disable-line class-methods-use-this
+ const routes = new Set;
+ for(const switchy of switches) {
+ if ( Array.isArray(switchy?.props?.children) )
+ for(const child of switchy.props.children) {
+ if ( child?.props?.component )
+ routes.add(child);
+ }
+ }
+
+ return routes;
}
@@ -46,7 +91,7 @@ export default class Switchboard extends Module {
if ( count > 50 )
return Promise.resolve(null);
- return new Promise(r => setTimeout(r, 50)).then(() => this.awaitRoute(count + 1));
+ return sleep(50).then(() => this.awaitRoute(count + 1));
}
@@ -57,16 +102,16 @@ export default class Switchboard extends Module {
// Find the current route.
const route = await this.awaitRoute(),
- da_switch = route && this.fine.searchParent(route, n => n.props && n.props.children);
+ da_switch = route && this.fine.searchParent(route, n => n.props?.children);
if ( ! da_switch )
- return new Promise(r => setTimeout(r, 50)).then(() => this.onEnable());
+ return sleep(50).then(() => this.onEnable());
// Identify Router
const router = await this.awaitRouter();
this.log.info(`Found Route and Switch with ${da_switch.props.children.length} routes.`);
- this.da_switch = da_switch;
+ this.possible = da_switch.props.children;
this.location = router.props.location.pathname;
//const location = router.props.location.pathname;
@@ -78,9 +123,31 @@ export default class Switchboard extends Module {
});
}
+ async startMultiRouter() {
+ this.multi_router = true;
+
+ const routes = await this.awaitRoutes();
+ if ( ! routes?.size )
+ return this.log.info(`Unable to find any s for multi-router.`);
+
+ const switches = this.getSwitches(routes);
+ if ( ! switches?.size )
+ return this.log.info(`Unable to find any switches for multi-router.`);
+
+ this.possible = this.getPossibleRoutes(switches);
+ this.log.info(`Found ${routes.size} Routes with ${switches.size} Switches and ${this.possible.size} routes.`);
+
+ this.loadOne();
+ }
+
loadOne() {
if ( ! this.loadRoute(false) )
- this.loadRoute(true);
+ if ( ! this.loadRoute(true) ) {
+ if ( ! this.multi_router )
+ this.startMultiRouter();
+ else
+ this.log.info(`There are no routes that can be used to load a chunk. Tried ${this.tried.size} routes.`);
+ }
}
waitAndSee() {
@@ -88,13 +155,13 @@ export default class Switchboard extends Module {
if ( this.web_munch._require )
return;
- this.log.info('We still need require(). Trying again.');
+ this.log.debug('We still need require(). Trying again.');
this.loadOne();
});
}
loadRoute(with_params) {
- for(const route of this.da_switch.props.children) {
+ for(const route of this.possible) {
if ( ! route.props || ! route.props.component )
continue;
@@ -114,7 +181,7 @@ export default class Switchboard extends Module {
}
this.tried.add(route.props.path);
- this.log.info('Found Non-Matching Route', route.props.path);
+ this.log.debug('Found Non-Matching Route', route.props.path);
const component_class = route.props.component;
@@ -124,7 +191,8 @@ export default class Switchboard extends Module {
try {
component = component_class.Preload({priority: 1});
} catch(err) {
- this.log.warn('Error instantiating preloader for forced chunk loading.', err);
+ this.log.warn('Error instantiating preloader for forced chunk loading.');
+ this.log.debug('Captured Error', err);
component = null;
}
@@ -133,18 +201,19 @@ export default class Switchboard extends Module {
try {
component.props.loader().then(() => {
- this.log.info('Successfully forced a chunk to load using route', route.props.path)
+ this.log.debug('Successfully loaded route', route.props.path)
this.waitAndSee();
});
} catch(err) {
- this.log.warn('Unexpected result trying to use component pre-loader to force loading of another chunk.');
+ this.log.warn('Unexpected result trying to use component pre-loader.');
}
} else {
try {
component = new route.props.component;
} catch(err) {
- this.log.warn('Error instantiating component for forced chunk loading.', err);
+ this.log.warn('Error instantiating component for forced chunk loading.');
+ this.log.debug('Captured Error', err);
component = null;
}
@@ -153,11 +222,11 @@ export default class Switchboard extends Module {
try {
component.props.children.props.loader().then(() => {
- this.log.info('Successfully forced a chunk to load using route', route.props.path)
+ this.log.debug('Successfully loaded route', route.props.path)
this.waitAndSee();
});
} catch(err) {
- this.log.warn('Unexpected result trying to use component loader to force loading of another chunk.');
+ this.log.warn('Unexpected result trying to use component loader.');
}
}
diff --git a/src/std-components/color-picker.vue b/src/std-components/color-picker.vue
index 75cbcfa1..936e0d93 100644
--- a/src/std-components/color-picker.vue
+++ b/src/std-components/color-picker.vue
@@ -47,7 +47,7 @@
v-if="open"
v-on-clickaway="closePicker"
:class="{'ffz-bottom-100': openUp}"
- class="tw-absolute tw-z-above ffz-balloon--up ffz-balloon--right"
+ class="tw-absolute tw-z-above tw-tooltip--down tw-tooltip--align-right"
>
diff --git a/src/utilities/compat/fine.js b/src/utilities/compat/fine.js
index b1d17ee7..d9aa1d2f 100644
--- a/src/utilities/compat/fine.js
+++ b/src/utilities/compat/fine.js
@@ -246,7 +246,7 @@ export default class Fine extends Module {
}
}
- searchTree(node, criteria, max_depth=15, depth=0, traverse_roots = true) {
+ searchTree(node, criteria, max_depth=15, depth=0, traverse_roots = true, multi = false) {
if ( ! node )
node = this.react;
else if ( node._reactInternalFiber )
@@ -254,8 +254,16 @@ export default class Fine extends Module {
else if ( node instanceof Node )
node = this.getReactInstance(node);
+ if ( multi ) {
+ if ( !(multi instanceof Set) )
+ multi = new Set;
+ }
+
+ if ( multi && ! (multi instanceof Set) )
+ multi = new Set;
+
if ( ! node || node._ffz_no_scan || depth > max_depth )
- return null;
+ return multi ? multi : null;
if ( typeof criteria === 'string' ) {
const wrapper = this._wrappers.get(criteria);
@@ -263,20 +271,24 @@ export default class Fine extends Module {
throw new Error('invalid critera');
if ( ! wrapper._class )
- return null;
+ return multi ? multi : null;
criteria = n => n && n.constructor === wrapper._class;
}
const inst = node.stateNode;
- if ( inst && criteria(inst, node) )
- return inst;
+ if ( inst && criteria(inst, node) ) {
+ if ( multi )
+ multi.add(inst);
+ else
+ return inst;
+ }
if ( node.child ) {
let child = node.child;
while(child) {
- const result = this.searchTree(child, criteria, max_depth, depth+1, traverse_roots);
- if ( result )
+ const result = this.searchTree(child, criteria, max_depth, depth+1, traverse_roots, multi);
+ if ( result && ! multi )
return result;
child = child.sibling;
}
@@ -287,14 +299,17 @@ export default class Fine extends Module {
if ( root ) {
let child = root._internalRoot && root._internalRoot.current || root.current;
while(child) {
- const result = this.searchTree(child, criteria, max_depth, depth+1, traverse_roots);
- if ( result )
+ const result = this.searchTree(child, criteria, max_depth, depth+1, traverse_roots, multi);
+ if ( result && ! multi )
return result;
child = child.sibling;
}
}
}
+
+ if ( multi )
+ return multi;
}
diff --git a/src/utilities/compat/subpump.js b/src/utilities/compat/subpump.js
index 638c8b0d..5eb84836 100644
--- a/src/utilities/compat/subpump.js
+++ b/src/utilities/compat/subpump.js
@@ -55,7 +55,7 @@ export default class Subpump extends Module {
return;
}
- for(const [key, val] of Object.entries(instances))
+ for(const val of Object.values(instances))
if ( val?._client ) {
if ( this.instance ) {
this.log.warn('Multiple PubSub instances detected. Things might act weird.');
diff --git a/src/utilities/compat/webmunch.js b/src/utilities/compat/webmunch.js
index 0d8017e3..6174b305 100644
--- a/src/utilities/compat/webmunch.js
+++ b/src/utilities/compat/webmunch.js
@@ -6,8 +6,20 @@
// ============================================================================
import Module from 'utilities/module';
-import {has, sleep} from 'utilities/object';
-import { DEBUG } from '../constants';
+import {has} from 'utilities/object';
+import { DEBUG } from 'utilities/constants';
+
+
+const Requires = Symbol('FFZRequires');
+
+const regex_cache = {};
+
+function getRequireRegex(name) {
+ if ( ! regex_cache[name] )
+ regex_cache[name] = new RegExp(`\\b${name}\\(([0-9a-zA-Z_+]+)\\)`, 'g');
+
+ return regex_cache[name];
+}
const NAMES = [
'webpackJsonp',
@@ -34,8 +46,11 @@ export default class WebMunch extends Module {
this._chunk_names = {};
this._mod_cache = {};
+ this._checked_module = {};
+ this._required_ids = new Set;
this._known_ids = new Set;
+ this.Requires = Requires;
this.v4 = null;
this.hookLoader();
@@ -107,12 +122,9 @@ export default class WebMunch extends Module {
this._original_loader = thing.push;
// Wrap all existing modules in case any of them haven't been required yet.
- // However, there's an issue with this causing loading issues on the
- // dashboard. Somehow. Not sure, so just don't do it on that page.
- if ( ! location.hostname.includes('dashboard') )
- for(const chunk of thing)
- if ( chunk && chunk[1] )
- this.processModulesV4(chunk[1]);
+ for(const chunk of thing)
+ if ( chunk && chunk[1] )
+ this.processModulesV4(chunk[1], true);
try {
thing.push = this.webpackJsonpv4.bind(this);
@@ -146,29 +158,40 @@ export default class WebMunch extends Module {
}
+ _resolveRequire(require) {
+ if ( this._require )
+ return;
+
+ this._require = require;
+ if ( this._resolve_require ) {
+ for(const fn of this._resolve_require)
+ fn(require);
+
+ this._resolve_require = null;
+ }
+ }
+
+
processModulesV4(modules) {
const t = this;
for(const [mod_id, original_module] of Object.entries(modules)) {
this._known_ids.add(mod_id);
+
modules[mod_id] = function(module, exports, require, ...args) {
if ( ! t._require && typeof require === 'function' ) {
- t.log.info(`require() grabbed from invocation of module ${mod_id}`);
- t._require = require;
- if ( t._resolve_require ) {
- try {
- for(const fn of t._resolve_require)
- fn(require);
- } catch(err) {
- t.log.error('An error occurred running require callbacks.', err);
- }
-
- t._resolve_require = null;
+ t.log.debug(`require() grabbed from invocation of module ${mod_id}`);
+ try {
+ t._resolveRequire(require);
+ } catch(err) {
+ t.log.error('An error occurred running require callbacks.', err);
}
}
return original_module.call(this, module, exports, require, ...args);
}
+
+ modules[mod_id].original = original_module;
}
}
@@ -176,14 +199,15 @@ export default class WebMunch extends Module {
webpackJsonpv4(data) {
const chunk_ids = data[0],
modules = data[1],
- names = Array.isArray(chunk_ids) && chunk_ids.map(x => this._chunk_names[x] || x).join(', ');
+ names = Array.isArray(chunk_ids) && chunk_ids.map(x => this._chunk_names[x] || x);
- this.log.verbose(`Twitch Chunk Loaded: ${chunk_ids} (${names})`);
+ this.log.verbose(`Twitch Chunk Loaded: ${chunk_ids} (${names.join(', ')})`);
this.log.verbose(`Modules: ${Object.keys(modules)}`);
if ( modules )
- this.processModulesV4(modules);
+ this.processModulesV4(modules, false);
+ this._checked_module = {};
const res = this._original_loader.apply(this._original_store, arguments); // eslint-disable-line prefer-rest-params
this.emit(':loaded', chunk_ids, names, modules);
return res;
@@ -296,10 +320,16 @@ export default class WebMunch extends Module {
if ( ! this._original_store )
return null;
+ const out = new Set;
+
for(const [chunks, modules] of this._original_store) {
- if ( modules[id] )
- return chunks;
+ if ( modules[id] ) {
+ for(const chunk of chunks)
+ out.add(chunk);
+ }
}
+
+ return [...out];
}
chunkNameForModule(id) {
@@ -316,6 +346,14 @@ export default class WebMunch extends Module {
return null;
}
+ chunkNamesForModule(id) {
+ const chunks = this._chunksForModule(id);
+ if ( ! chunks )
+ return null;
+
+ return chunks.map(id => this._chunk_names[id] || id);
+ }
+
_oldGetModule(key, predicate, require) {
if ( ! require || ! require.c )
@@ -358,7 +396,7 @@ export default class WebMunch extends Module {
if ( mod ) {
const ret = predicate(mod);
if ( ret ) {
- this.log.debug(`Located module "${key}" in module ${k}${DEBUG ? ` (${this.chunkNameForModule(k)})` : ''} after ${checked} tries`);
+ this.log.debug(`[Old] Located module "${key}" in module ${k}${DEBUG ? ` (${this.chunkNameForModule(k)})` : ''} after ${checked} tries`);
const out = predicate.use_result ? ret : mod;
if ( key )
this._mod_cache[key] = out;
@@ -367,7 +405,7 @@ export default class WebMunch extends Module {
}
}
- this.log.debug(`Unable to locate module "${key}"`);
+ this.log.debug(`[Old] Unable to locate module "${key}" despite checking ${checked} modules`);
return null;
}
@@ -405,6 +443,22 @@ export default class WebMunch extends Module {
for(const id of ids) {
try {
checked++;
+
+ // If we have not previously required this module, check to see
+ // if we CAN require this module. We want to avoid requiring a
+ // module that doesn't yet have a constructor because that will
+ // break webpack's internal state.
+ if ( ! this._required_ids.has(id) ) {
+ let check = this._checked_module[id];
+ if ( check == null )
+ check = this._checkModule(id);
+
+ if ( check )
+ continue;
+ }
+
+ this._required_ids.add(id);
+
const mod = require(id);
if ( mod ) {
const ret = predicate(mod);
@@ -421,11 +475,50 @@ export default class WebMunch extends Module {
}
}
- this.log.debug(`Unable to locate module "${key}"`);
+ this.log.debug(`Unable to locate module "${key}" despite checking ${checked} modules`);
return null;
}
+ _checkModule(id) {
+ const fn = this._require?.m?.[id];
+ if ( fn ) {
+ let reqs = fn[Requires],
+ banned = false;
+
+ if ( reqs == null ) {
+ const str = fn.toString(),
+ name_match = /^function\([^,)]+,[^,)]+,([^,)]+)/.exec(str);
+
+ if ( name_match ) {
+ const regex = getRequireRegex(name_match[1]);
+ reqs = fn[Requires] = new Set;
+
+ regex.lastIndex = 0;
+ let match;
+
+ while((match = regex.exec(str))) {
+ const mod_id = match[1];
+ reqs.add(mod_id);
+
+ if ( ! this._require.m[mod_id] )
+ banned = true;
+ }
+
+ } else
+ fn[Requires] = false;
+
+ } else if ( reqs ) {
+ for(const mod_id of reqs)
+ if ( ! this._require.m[mod_id] )
+ banned = true;
+ }
+
+ return this._checked_module[id] = banned;
+ }
+ }
+
+
// ========================================================================
// Grabbing Require
// ========================================================================
@@ -515,9 +608,11 @@ export default class WebMunch extends Module {
}
this._chunk_names = modules;
- this.log.info(`Loaded names for ${Object.keys(modules).length} chunks from require().`)
+ this.log.debug(`Loaded names for ${Object.keys(modules).length} chunks from require().`)
} else
this.log.warn(`Unable to find chunk names in require().`);
}
-}
\ No newline at end of file
+}
+
+WebMunch.Requires = Requires;
\ No newline at end of file
diff --git a/src/utilities/logging.js b/src/utilities/logging.js
index e647ef9a..06df7456 100644
--- a/src/utilities/logging.js
+++ b/src/utilities/logging.js
@@ -1,5 +1,7 @@
'use strict';
+import { has } from 'utilities/object';
+
const RAVEN_LEVELS = {
1: 'debug',
2: 'info',
@@ -14,7 +16,7 @@ function readLSLevel() {
return null;
const upper = level.toUpperCase();
- if ( Logger[upper] )
+ if ( has(Logger, upper) )
return Logger[upper];
if ( /^\d+$/.test(level) )