1
0
Fork 0
mirror of https://github.com/FrankerFaceZ/FrankerFaceZ.git synced 2025-06-27 21:05:53 +00:00
* Added: Option to hide channels from the directory based on their title.
* Fixed: Issue with FrankerFaceZ failing to initialize correctly due to a change in Twitch's JS layout.
This commit is contained in:
SirStendec 2022-06-08 23:07:07 -04:00
parent 213c2195cc
commit bcee12a6b3
8 changed files with 158 additions and 25 deletions

View file

@ -1,7 +1,7 @@
{
"name": "frankerfacez",
"author": "Dan Salvato LLC",
"version": "4.33.6",
"version": "4.34.0",
"description": "FrankerFaceZ is a Twitch enhancement suite.",
"private": true,
"license": "Apache-2.0",

View file

@ -8,7 +8,7 @@ import dayjs from 'dayjs';
import Module from 'utilities/module';
import {createElement, ManagedStyle} from 'utilities/dom';
import {timeout, has, glob_to_regex, escape_regex, split_chars} from 'utilities/object';
import {timeout, has, addWordSeparators, glob_to_regex, escape_regex, split_chars} from 'utilities/object';
import {Color} from 'utilities/color';
import Badges from './badges';
@ -24,8 +24,6 @@ import * as RICH_PROVIDERS from './rich_providers';
import Actions from './actions';
import { getFontsList } from 'src/utilities/fonts';
export const SEPARATORS = '[\\s`~<>!-#%-\\x2A,-/:;\\x3F@\\x5B-\\x5D_\\x7B}\\u00A1\\u00A7\\u00AB\\u00B6\\u00B7\\u00BB\\u00BF\\u037E\\u0387\\u055A-\\u055F\\u0589\\u058A\\u05BE\\u05C0\\u05C3\\u05C6\\u05F3\\u05F4\\u0609\\u060A\\u060C\\u060D\\u061B\\u061E\\u061F\\u066A-\\u066D\\u06D4\\u0700-\\u070D\\u07F7-\\u07F9\\u0830-\\u083E\\u085E\\u0964\\u0965\\u0970\\u0AF0\\u0DF4\\u0E4F\\u0E5A\\u0E5B\\u0F04-\\u0F12\\u0F14\\u0F3A-\\u0F3D\\u0F85\\u0FD0-\\u0FD4\\u0FD9\\u0FDA\\u104A-\\u104F\\u10FB\\u1360-\\u1368\\u1400\\u166D\\u166E\\u169B\\u169C\\u16EB-\\u16ED\\u1735\\u1736\\u17D4-\\u17D6\\u17D8-\\u17DA\\u1800-\\u180A\\u1944\\u1945\\u1A1E\\u1A1F\\u1AA0-\\u1AA6\\u1AA8-\\u1AAD\\u1B5A-\\u1B60\\u1BFC-\\u1BFF\\u1C3B-\\u1C3F\\u1C7E\\u1C7F\\u1CC0-\\u1CC7\\u1CD3\\u2010-\\u2027\\u2030-\\u2043\\u2045-\\u2051\\u2053-\\u205E\\u207D\\u207E\\u208D\\u208E\\u2329\\u232A\\u2768-\\u2775\\u27C5\\u27C6\\u27E6-\\u27EF\\u2983-\\u2998\\u29D8-\\u29DB\\u29FC\\u29FD\\u2CF9-\\u2CFC\\u2CFE\\u2CFF\\u2D70\\u2E00-\\u2E2E\\u2E30-\\u2E3B\\u3001-\\u3003\\u3008-\\u3011\\u3014-\\u301F\\u3030\\u303D\\u30A0\\u30FB\\uA4FE\\uA4FF\\uA60D-\\uA60F\\uA673\\uA67E\\uA6F2-\\uA6F7\\uA874-\\uA877\\uA8CE\\uA8CF\\uA8F8-\\uA8FA\\uA92E\\uA92F\\uA95F\\uA9C1-\\uA9CD\\uA9DE\\uA9DF\\uAA5C-\\uAA5F\\uAADE\\uAADF\\uAAF0\\uAAF1\\uABEB\\uFD3E\\uFD3F\\uFE10-\\uFE19\\uFE30-\\uFE52\\uFE54-\\uFE61\\uFE63\\uFE68\\uFE6A\\uFE6B\\uFF01-\\uFF03\\uFF05-\\uFF0A\\uFF0C-\\uFF0F\\uFF1A\\uFF1B\\uFF1F\\uFF20\\uFF3B-\\uFF3D\\uFF3F\\uFF5B\\uFF5D\\uFF5F-\\uFF65]';
function sortPriorityColorTerms(list) {
list.sort((a,b) => {
if ( a[0] < b[0] ) return 1;
@ -37,10 +35,6 @@ function sortPriorityColorTerms(list) {
return list;
}
function addSeparators(str) {
return `(^|.*?${SEPARATORS})(?:${str})(?=$|${SEPARATORS})`
}
const TERM_FLAGS = ['g', 'gi'];
function formatTerms(data) {
@ -49,7 +43,7 @@ function formatTerms(data) {
for(let i=0; i < data.length; i++) {
const list = data[i];
if ( list[0].length )
list[1].push(addSeparators(list[0].join('|')));
list[1].push(addWordSeparators(list[0].join('|')));
out.push(list[1].length ? new RegExp(list[1].join('|'), TERM_FLAGS[i] || 'gi') : null);
}

View file

@ -249,7 +249,7 @@ Twilight.KNOWN_MODULES = {
}
}
const VEND_CHUNK = n => n && n.includes('vendor');
const VEND_CHUNK = n => ! n || n.includes('vendor');
Twilight.KNOWN_MODULES.core.use_result = true;
//Twilight.KNOWN_MODULES.core.chunks = 'core';
@ -263,7 +263,7 @@ Twilight.KNOWN_MODULES['gql-printer'].chunks = VEND_CHUNK;
Twilight.KNOWN_MODULES.mousetrap.chunks = VEND_CHUNK;
const CHAT_CHUNK = n => n && n.includes('chat');
const CHAT_CHUNK = n => ! n || n.includes('chat');
Twilight.KNOWN_MODULES['chat-types'].use_result = true;
Twilight.KNOWN_MODULES['chat-types'].chunks = CHAT_CHUNK;

View file

@ -7,7 +7,7 @@
import {SiteModule} from 'utilities/module';
import {duration_to_string} from 'utilities/time';
import {createElement} from 'utilities/dom';
import {get} from 'utilities/object';
import {get, glob_to_regex, escape_regex, addWordSeparators} from 'utilities/object';
import Game from './game';
@ -18,6 +18,15 @@ export const CARD_CONTEXTS = ((e ={}) => {
return e;
})();
function formatTerms(data, flags) {
if ( data[0].length )
data[1].push(addWordSeparators(data[0].join('|')));
if ( ! data[1].length )
return null;
return new RegExp(data[1].join('|'), flags);
}
//const CREATIVE_ID = 488191;
@ -175,6 +184,58 @@ export default class Directory extends SiteModule {
changed: () => this.DirectoryShelf.forceUpdate()
});
this.settings.add('directory.block-titles', {
default: [],
type: 'array_merge',
always_inherit: true,
ui: {
path: 'Directory > Channels >> Block by Title',
component: 'basic-terms'
}
});
this.settings.add('__filter:directory.block-titles', {
requires: ['directory.block-titles'],
equals: 'requirements',
process(ctx) {
const val = ctx.get('directory.block-titles');
if ( ! val || ! val.length )
return null;
const out = [
[ // sensitive
[], [] // word
],
[
[], []
]
];
for(const item of val) {
const t = item.t;
let v = item.v;
if ( t === 'glob' )
v = glob_to_regex(v);
else if ( t !== 'raw' )
v = escape_regex(v);
if ( ! v || ! v.length )
continue;
out[item.s ? 0 : 1][item.w ? 0 : 1].push(v);
}
return [
formatTerms(out[0], 'g'),
formatTerms(out[1], 'gi')
];
},
changed: () => this.updateCards()
});
/*this.settings.add('directory.hide-viewing-history', {
default: false,
ui: {
@ -339,9 +400,23 @@ export default class Directory extends SiteModule {
}
}
const should_hide = bad_tag || (props.streamType === 'rerun' && this.settings.get('directory.hide-vodcasts')) ||
(props.context != null && props.context !== CARD_CONTEXTS.SingleGameList && this.settings.provider.get('directory.game.blocked-games', []).includes(game)) ||
((props.isPromotion || props.sourceType === 'COMMUNITY_BOOST' || props.sourceType === 'PROMOTION' || props.sourceType === 'SPONSORED') && this.settings.get('directory.hide-promoted'));
let should_hide = false;
if ( bad_tag )
should_hide = true;
else if ( props.streamType === 'rerun' && this.settings.get('directory.hide-vodcasts') )
should_hide = true;
else if ( props.context != null && props.context !== CARD_CONTEXTS.SingleGameList && this.settings.provider.get('directory.game.blocked-games', []).includes(game) )
should_hide = true;
else if ( (props.isPromotion || props.sourceType === 'COMMUNITY_BOOST' || props.sourceType === 'PROMOTION' || props.sourceType === 'SPONSORED') && this.settings.get('directory.hide-promoted') )
should_hide = true;
else {
const regexes = this.settings.get('__filter:directory.block-titles');
if ( regexes &&
(( regexes[0] && regexes[0].test(props.title) ) ||
( regexes[1] && regexes[1].test(props.title) ))
)
should_hide = true;
}
let hide_container = el.closest('.tw-tower > div');
if ( ! hide_container )

View file

@ -359,9 +359,24 @@ export default class Layout extends Module {
game = stream?.game?.displayName,
offline = props?.offline ?? false;
let should_hide = false;
if ( game && blocked_games.includes(game) )
should_hide = true;
if ( props.isPromoted && this.settings.get('directory.hide-promoted') )
should_hide = true;
else {
const regexes = this.settings.get('__filter:directory.block-titles');
const title = stream?.broadcaster?.broadcastSettings?.title;
if ( regexes && title &&
(( regexes[0] && regexes[0].test(title) ) ||
( regexes[1] && regexes[1].test(title) ))
)
should_hide = true;
}
card.classList.toggle('ffz--side-nav-card-rerun', rerun);
card.classList.toggle('ffz--side-nav-card-offline', offline);
card.classList.toggle('tw-hide', game ? blocked_games.includes(game) : false);
card.classList.toggle('tw-hide', should_hide);
}
}

View file

@ -222,7 +222,12 @@ export default class WebMunch extends Module {
if ( modules )
this.processModulesV4(modules, false);
this._checked_module = {};
for(const [key,val] of Object.entries(this._checked_module)) {
if (val == true)
this._checked_module[key] = null;
}
//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;
@ -375,7 +380,7 @@ export default class WebMunch extends Module {
return null;
let ids;
if ( this._original_store && predicate.chunks ) {
if ( this._original_store && predicate.chunks && this._chunk_names && Object.keys(this._chunk_names).length ) {
const chunk_pred = typeof predicate.chunks === 'function';
if ( ! chunk_pred && ! Array.isArray(predicate.chunks) )
predicate.chunks = [predicate.chunks];
@ -429,7 +434,7 @@ export default class WebMunch extends Module {
return null;
let ids = this._known_ids;
if ( this._original_store && predicate.chunks ) {
if ( this._original_store && predicate.chunks && this._chunk_names && Object.keys(this._chunk_names).length ) {
const chunk_pred = typeof predicate.chunks === 'function';
if ( ! chunk_pred && ! Array.isArray(predicate.chunks) )
predicate.chunks = [predicate.chunks];
@ -498,9 +503,19 @@ export default class WebMunch extends Module {
}
_checkModule(id) {
const fn = this._require?.m?.[id];
_checkModule(id, checked = null) {
if (checked) {
if (checked.has(id))
return (this._checked_module[id] ?? false);
checked.add(id);
}
let fn = this._require?.m?.[id];
if ( fn ) {
if ( fn.original )
fn = fn.original;
let reqs = fn[Requires],
banned = false;
@ -543,12 +558,40 @@ export default class WebMunch extends Module {
} else if ( reqs ) {
for(const mod_id of reqs)
if ( ! this._require.m[mod_id] )
if ( ! this._require.m[mod_id] ) {
banned = true;
break;
}
}
if ( ! banned && reqs ) {
if ( ! checked ) {
checked = new Set();
checked.add(id);
}
for(const mod_id of reqs) {
let val = this._checked_module[mod_id];
if (val == null && ! checked.has(mod_id))
try {
val = this._checkModule(mod_id, checked);
} catch (err) {
this.log.verbose(`Recursion error checking module ${id} (${mod_id})`);
val = true;
}
if ( val ) {
this.log.verbose(`Unable to load module ${id} due to unable to load dependency ${mod_id}`);
banned = true;
break;
}
}
}
return this._checked_module[id] = banned;
}
return this._checked_module[id] = true;
}
@ -604,7 +647,7 @@ export default class WebMunch extends Module {
const loader = require.e && require.e.toString();
let modules;
if ( loader && loader.indexOf('Loading chunk') !== -1 ) {
const data = this.v4 ? /assets\/"\+\(({1:.*?})/.exec(loader) : /({0:.*?})/.exec(loader);
const data = this.v4 ? /assets\/"\+\(?({1:.*?})/.exec(loader) : /({0:.*?})/.exec(loader);
if ( data )
try {
modules = JSON.parse(data[1].replace(/(\d+):/g, '"$1":'))
@ -612,7 +655,7 @@ export default class WebMunch extends Module {
} else if ( require.u ) {
const builder = require.u.toString(),
match = /assets\/"\+({\d+:.*?})/.exec(builder),
match = /assets\/"\+\(?({\d+:.*?})/.exec(builder),
data = match ? match[1].replace(/([\de]+):/g, (_, m) => {
if ( /^\d+e\d+$/.test(m) ) {
const bits = m.split('e');

View file

@ -17,6 +17,7 @@ export const SENTRY_ID = 'https://74b46b3894114f399d51949c6d237489@sentry.franke
export const LV_SERVER = 'https://cbenni.com/api';
export const LV_SOCKET_SERVER = 'wss://cbenni.com/socket.io/';
export const WORD_SEPARATORS = '[\\s`~<>!-#%-\\x2A,-/:;\\x3F@\\x5B-\\x5D_\\x7B}\\u00A1\\u00A7\\u00AB\\u00B6\\u00B7\\u00BB\\u00BF\\u037E\\u0387\\u055A-\\u055F\\u0589\\u058A\\u05BE\\u05C0\\u05C3\\u05C6\\u05F3\\u05F4\\u0609\\u060A\\u060C\\u060D\\u061B\\u061E\\u061F\\u066A-\\u066D\\u06D4\\u0700-\\u070D\\u07F7-\\u07F9\\u0830-\\u083E\\u085E\\u0964\\u0965\\u0970\\u0AF0\\u0DF4\\u0E4F\\u0E5A\\u0E5B\\u0F04-\\u0F12\\u0F14\\u0F3A-\\u0F3D\\u0F85\\u0FD0-\\u0FD4\\u0FD9\\u0FDA\\u104A-\\u104F\\u10FB\\u1360-\\u1368\\u1400\\u166D\\u166E\\u169B\\u169C\\u16EB-\\u16ED\\u1735\\u1736\\u17D4-\\u17D6\\u17D8-\\u17DA\\u1800-\\u180A\\u1944\\u1945\\u1A1E\\u1A1F\\u1AA0-\\u1AA6\\u1AA8-\\u1AAD\\u1B5A-\\u1B60\\u1BFC-\\u1BFF\\u1C3B-\\u1C3F\\u1C7E\\u1C7F\\u1CC0-\\u1CC7\\u1CD3\\u2010-\\u2027\\u2030-\\u2043\\u2045-\\u2051\\u2053-\\u205E\\u207D\\u207E\\u208D\\u208E\\u2329\\u232A\\u2768-\\u2775\\u27C5\\u27C6\\u27E6-\\u27EF\\u2983-\\u2998\\u29D8-\\u29DB\\u29FC\\u29FD\\u2CF9-\\u2CFC\\u2CFE\\u2CFF\\u2D70\\u2E00-\\u2E2E\\u2E30-\\u2E3B\\u3001-\\u3003\\u3008-\\u3011\\u3014-\\u301F\\u3030\\u303D\\u30A0\\u30FB\\uA4FE\\uA4FF\\uA60D-\\uA60F\\uA673\\uA67E\\uA6F2-\\uA6F7\\uA874-\\uA877\\uA8CE\\uA8CF\\uA8F8-\\uA8FA\\uA92E\\uA92F\\uA95F\\uA9C1-\\uA9CD\\uA9DE\\uA9DF\\uAA5C-\\uAA5F\\uAADE\\uAADF\\uAAF0\\uAAF1\\uABEB\\uFD3E\\uFD3F\\uFE10-\\uFE19\\uFE30-\\uFE52\\uFE54-\\uFE61\\uFE63\\uFE68\\uFE6A\\uFE6B\\uFF01-\\uFF03\\uFF05-\\uFF0A\\uFF0C-\\uFF0F\\uFF1A\\uFF1B\\uFF1F\\uFF20\\uFF3B-\\uFF3D\\uFF3F\\uFF5B\\uFF5D\\uFF5F-\\uFF65]';
export const BAD_HOTKEYS = [
'f',

View file

@ -1,6 +1,6 @@
'use strict';
import {BAD_HOTKEYS, TWITCH_EMOTE_V2} from 'utilities/constants';
import {BAD_HOTKEYS, TWITCH_EMOTE_V2, WORD_SEPARATORS} from 'utilities/constants';
const HOP = Object.prototype.hasOwnProperty;
@ -539,6 +539,11 @@ export const escape_regex = RegExp.escape || function escape_regex(str) {
}
export function addWordSeparators(str) {
return `(^|.*?${WORD_SEPARATORS})(?:${str})(?=$|${WORD_SEPARATORS})`
}
const CONTROL_CHARS = '/$^+.()=!|';
export function glob_to_regex(input) {