diff --git a/package.json b/package.json index 6348a6cb..54411e83 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "frankerfacez", "author": "Dan Salvato LLC", - "version": "4.69.0", + "version": "4.70.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 2e9f63dc..c7e68bc7 100644 --- a/src/modules/chat/index.js +++ b/src/modules/chat/index.js @@ -11,7 +11,7 @@ import Module, { buildAddonProxy } from 'utilities/module'; import {Color} from 'utilities/color'; import {createElement, ManagedStyle} from 'utilities/dom'; import {getFontsList} from 'utilities/fonts'; -import {timeout, has, addWordSeparators, glob_to_regex, escape_regex, split_chars, makeAddonIdChecker, deep_copy} from 'utilities/object'; +import {timeout, has, addWordSeparators, glob_to_regex, escape_regex, split_chars, makeAddonIdChecker, deep_copy, SourcedSet} from 'utilities/object'; import Badges from './badges'; import Emotes from './emotes'; @@ -93,6 +93,9 @@ export default class Chat extends Module { this.context = this.settings.context({}); + this.CommandPrefixes = new SourcedSet(true); + this.CommandPrefixes.set('ffz', ['/', '!']); + this.rooms = {}; this.users = {}; @@ -614,7 +617,7 @@ export default class Chat extends Module { if ( ! data.length ) return null; - return new RegExp(`^(?:${data.join('|')})$`, 'gi'); + return new RegExp(`^(?:${data.join('|')})$`, 'i'); }); } }); @@ -1414,6 +1417,24 @@ export default class Chat extends Module { ); } + overrides.addTabCommandPrefix = (prefix, provider = null) => { + if ( provider == null ) + provider = addon_id; + if ( is_dev && provider !== addon_id ) + module.log.warn('[DEV-CHECK] Used addTabCommandPrefix with incorrect provider.'); + + return this.addTabCommandPrefix(prefix, provider); + } + + overrides.removeTabCommandPrefix = (prefix, provider = null) => { + if ( provider == null ) + provider = addon_id; + if ( is_dev && provider !== addon_id ) + module.log.warn('[DEV-CHECK] Used removeTabCommandPrefix with incorrect provider.'); + + return this.removeTabCommandPrefix(prefix, provider); + } + overrides.addTokenizer = tokenizer => { if ( tokenizer ) tokenizer.__source = addon_id; @@ -1580,6 +1601,8 @@ export default class Chat extends Module { } } + this.CommandPrefixes.delete(addon_id); + for(const item of this.iterateAllRoomsAndUsers()) removed += item._unloadAddon(addon_id) ?? 0; @@ -2305,6 +2328,26 @@ export default class Chat extends Module { return Object.values(this._hl_reasons); } + addTabCommandPrefix(prefix, source = 'ffz') { + if ( ! Array.isArray(prefix) ) + prefix = [prefix]; + + for(const item of prefix) { + if ( typeof item !== 'string' || item.length !== 1 ) + throw new Error('Invalid command prefix. Must be string of length 1.'); + + this.CommandPrefixes.push(source, item); + } + } + + removeTabCommandPrefix(prefix, source = 'ffz') { + if ( ! Array.isArray(prefix) ) + prefix = [prefix]; + + for(const item of prefix) + this.CommandPrefixes.remove(source, item); + } + addTokenizer(tokenizer) { const type = tokenizer.type; if ( has(this.tokenizers, type) ) { diff --git a/src/sites/twitch-twilight/modules/chat/index.js b/src/sites/twitch-twilight/modules/chat/index.js index 1eead1fa..d72f6bac 100644 --- a/src/sites/twitch-twilight/modules/chat/index.js +++ b/src/sites/twitch-twilight/modules/chat/index.js @@ -461,7 +461,7 @@ export default class ChatHook extends Module { } }); - this.settings.add('chat.banners.kappa-train', { + /*this.settings.add('chat.banners.kappa-train', { default: false, ui: { path: 'Chat > Appearance >> Community', @@ -469,6 +469,15 @@ export default class ChatHook extends Module { description: '**Note**: This setting is currently theoretical and may not work, or may cause non-Kappa hype trains to appear. Due to the infrequent nature of hype trains, and especially the golden kappa hype train, it is very hard to test.', component: 'setting-check-box' } + });*/ + + this.settings.add('chat.banners.pinned-message', { + default: true, + ui: { + path: 'Chat > Appearance >> Community', + title: 'Allow Pinned Messages to be displayed in chat.', + component: 'setting-check-box' + } }); this.settings.add('chat.banners.drops', { @@ -1042,6 +1051,7 @@ export default class ChatHook extends Module { 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.banners.drops', this.cleanHighlights, this); + this.chat.context.on('changed:chat.banners.pinned-message', this.cleanHighlights, this); this.chat.context.on('changed:chat.disable-handling', this.updateDisableHandling, this); @@ -1722,6 +1732,7 @@ export default class ChatHook extends Module { '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'), + 'pinned_chat': this.chat.context.get('chat.banners.pinned-message'), 'mw-drop-available': this.chat.context.get('chat.banners.drops') }; @@ -1736,8 +1747,8 @@ export default class ChatHook extends Module { const type = entry.event.type; if ( type && has(types, type) && ! types[type] ) { // Attempt to allow Golden Kappa hype trains? - if ( type === 'hype_train' && entry.event.typeDetails === '0' && this.chat.context.get('chat.banners.kappa-train') ) - continue; + //if ( type === 'hype_train' && entry.event.typeDetails === '0' && this.chat.context.get('chat.banners.kappa-train') ) + // continue; this.log.info('Removing community highlight: ', type, '#', entry.id); this.community_dispatch({ diff --git a/src/sites/twitch-twilight/modules/chat/input.jsx b/src/sites/twitch-twilight/modules/chat/input.jsx index acd7c37b..6864991f 100644 --- a/src/sites/twitch-twilight/modules/chat/input.jsx +++ b/src/sites/twitch-twilight/modules/chat/input.jsx @@ -7,15 +7,11 @@ import Module from 'utilities/module'; import { findReactFragment } from 'utilities/dom'; -import { getTwitchEmoteSrcSet } from 'utilities/object'; +import { SourcedSet, getTwitchEmoteSrcSet } from 'utilities/object'; import { TWITCH_POINTS_SETS, TWITCH_GLOBAL_SETS, TWITCH_PRIME_SETS, KNOWN_CODES, REPLACEMENTS, REPLACEMENT_BASE, KEYS } from 'utilities/constants'; import Twilight from 'site'; -const COMMAND_KEYS = [ - '!', - '/' -]; // Prefer using these statically-allocated collators to String.localeCompare const locale = Intl.Collator(); @@ -75,7 +71,6 @@ export default class Input extends Module { this.inject('site.fine'); this.inject('site'); - // Settings this.settings.add('chat.hype.display-input', { @@ -783,7 +778,7 @@ export default class Input extends Module { inst.getMatches = function(input, unknown, index) { try { - return index === 0 && COMMAND_KEYS.includes(input[0]) + return index === 0 && t.chat.CommandPrefixes.includes(input[0]) ? inst.getCommands(input) : null; } catch(err) { @@ -797,11 +792,22 @@ export default class Input extends Module { isEditor: inst.props.isCurrentUserEditor }); + // Get the parent-input so we can do stuff. + const parent = t.fine.searchParent(inst, + n => n?.props?.channelID && n?.props?.setTray, 50); + const event = t.makeEvent({ input, permissionLevel: inst.props.permissionLevel, isEditor: inst.props.isCurrentUserEditor, - commands + commands, + + // Extra details, if we managed to find our parent. + __input: parent, + channel: parent ? { + id: parent.props.channelID, + login: parent.props.channelLogin + } : null }); t.emit('chat:get-tab-commands', event); diff --git a/src/sites/twitch-twilight/modules/directory/index.jsx b/src/sites/twitch-twilight/modules/directory/index.jsx index 10858863..46e84e9a 100644 --- a/src/sites/twitch-twilight/modules/directory/index.jsx +++ b/src/sites/twitch-twilight/modules/directory/index.jsx @@ -616,17 +616,17 @@ export default class Directory extends Module { // Are we getting a clip, a video, or a stream? if ( props.slug ) { // Clip - console.log('need flags for clip', props.slug); + //console.log('need flags for clip', props.slug); el._ffz_flags = []; } else if ( props.vodID ) { // Video - console.log('need flags for vod', props.vodID); + //console.log('need flags for vod', props.vodID); el._ffz_flags = []; } else { // Stream? - console.log('need flags for stream', props.channelLogin); + //console.log('need flags for stream', props.channelLogin); this.twitch_data.getStreamFlags(null, props.channelLogin).then(data => { el._ffz_flags = data ?? []; this.updateCard(el); @@ -670,11 +670,15 @@ export default class Directory extends Module { } if ( ! should_blur ) { const regexes = this.settings.get('__filter:directory.blur-titles'); - if ( regexes && - (( regexes[0] && regexes[0].test(props.title) ) || - ( regexes[1] && regexes[1].test(props.title) )) - ) - should_blur = true; + if ( regexes ) { + if ( regexes[0] ) + regexes[0].lastIndex = -1; + if ( regexes[1] ) + regexes[1].lastIndex = -1; + + if (( regexes[0] && regexes[0].test(props.title) ) || ( regexes[1] && regexes[1].test(props.title) )) + should_blur = true; + } } el.classList.toggle('ffz-hide-thumbnail', should_blur); @@ -700,11 +704,15 @@ export default class Directory extends Module { if ( ! should_hide ) { 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; + if ( regexes ) { + if ( regexes[0] ) + regexes[0].lastIndex = -1; + if ( regexes[1] ) + regexes[1].lastIndex = -1; + + if (( regexes[0] && regexes[0].test(props.title) ) || ( regexes[1] && regexes[1].test(props.title) )) + should_hide = true; + } } } diff --git a/src/sites/twitch-twilight/modules/layout.js b/src/sites/twitch-twilight/modules/layout.js index 4237dce5..e75e01c5 100644 --- a/src/sites/twitch-twilight/modules/layout.js +++ b/src/sites/twitch-twilight/modules/layout.js @@ -385,11 +385,14 @@ export default class Layout extends Module { 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; + if ( regexes && title ) { + if ( regexes[0] ) + regexes[0].lastIndex = -1; + if ( regexes[1] ) + regexes[1].lastIndex = -1; + if ( (regexes[0] && regexes[0].test(title)) || (regexes[1] && regexes[1].test(title)) ) + should_hide = true; + } } card.classList.toggle('ffz--side-nav-card-rerun', rerun); @@ -421,4 +424,4 @@ export default class Layout extends Module { updatePortraitMode() { } -} \ No newline at end of file +}