diff --git a/package.json b/package.json index ef57be86..89826189 100755 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/modules/chat/index.js b/src/modules/chat/index.js index a62c0570..702ee3fc 100644 --- a/src/modules/chat/index.js +++ b/src/modules/chat/index.js @@ -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); } diff --git a/src/sites/twitch-twilight/index.js b/src/sites/twitch-twilight/index.js index a92af90b..c63f0bbe 100644 --- a/src/sites/twitch-twilight/index.js +++ b/src/sites/twitch-twilight/index.js @@ -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; diff --git a/src/sites/twitch-twilight/modules/directory/index.jsx b/src/sites/twitch-twilight/modules/directory/index.jsx index 42c389b8..354916bf 100644 --- a/src/sites/twitch-twilight/modules/directory/index.jsx +++ b/src/sites/twitch-twilight/modules/directory/index.jsx @@ -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 ) diff --git a/src/sites/twitch-twilight/modules/layout.js b/src/sites/twitch-twilight/modules/layout.js index 7ca3418b..614633ed 100644 --- a/src/sites/twitch-twilight/modules/layout.js +++ b/src/sites/twitch-twilight/modules/layout.js @@ -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); } } diff --git a/src/utilities/compat/webmunch.js b/src/utilities/compat/webmunch.js index 791d2aab..c62d3a3a 100644 --- a/src/utilities/compat/webmunch.js +++ b/src/utilities/compat/webmunch.js @@ -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'); diff --git a/src/utilities/constants.js b/src/utilities/constants.js index bd0bcbdf..c1724aab 100644 --- a/src/utilities/constants.js +++ b/src/utilities/constants.js @@ -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', diff --git a/src/utilities/object.js b/src/utilities/object.js index 37916dd5..21f7c518 100644 --- a/src/utilities/object.js +++ b/src/utilities/object.js @@ -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) {