1
0
Fork 0
mirror of https://github.com/FrankerFaceZ/FrankerFaceZ.git synced 2025-09-16 01:56:55 +00:00
* Added: Option to prevent Pinned Messages from being displayed. Closes #1470
* Removed: Setting to allow the Golden Kappa Train to appear. This wasn't working correctly, so nothing of value has been lost.
* Fixed: Blocking chat users by username worked inconsistently.
* Fixed: Hiding channels in the directory by title worked inconsistently. Closes #1473
* API Changed: The `chat:get-tab-commands` event now has a `channel` object with the ID and login of the current channel, as well as a reference to the relevant input component.
* API Added: The `chat` module now has `addTabCommandPrefix(prefix: string | string[])` and `removeTabCommandPrefix(prefix: string | string[])` methods. By default, tab-completion for chat commands only triggers with the `/` and `!` prefix for performance reasons. This can be used to add additional prefix characters.
This commit is contained in:
SirStendec 2024-03-19 16:36:44 -04:00
parent 8807e09ea3
commit 10d35468a9
6 changed files with 104 additions and 33 deletions

View file

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

View file

@ -11,7 +11,7 @@ import Module, { buildAddonProxy } from 'utilities/module';
import {Color} from 'utilities/color'; import {Color} from 'utilities/color';
import {createElement, ManagedStyle} from 'utilities/dom'; import {createElement, ManagedStyle} from 'utilities/dom';
import {getFontsList} from 'utilities/fonts'; 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 Badges from './badges';
import Emotes from './emotes'; import Emotes from './emotes';
@ -93,6 +93,9 @@ export default class Chat extends Module {
this.context = this.settings.context({}); this.context = this.settings.context({});
this.CommandPrefixes = new SourcedSet(true);
this.CommandPrefixes.set('ffz', ['/', '!']);
this.rooms = {}; this.rooms = {};
this.users = {}; this.users = {};
@ -614,7 +617,7 @@ export default class Chat extends Module {
if ( ! data.length ) if ( ! data.length )
return null; 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 => { overrides.addTokenizer = tokenizer => {
if ( tokenizer ) if ( tokenizer )
tokenizer.__source = addon_id; 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()) for(const item of this.iterateAllRoomsAndUsers())
removed += item._unloadAddon(addon_id) ?? 0; removed += item._unloadAddon(addon_id) ?? 0;
@ -2305,6 +2328,26 @@ export default class Chat extends Module {
return Object.values(this._hl_reasons); 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) { addTokenizer(tokenizer) {
const type = tokenizer.type; const type = tokenizer.type;
if ( has(this.tokenizers, type) ) { if ( has(this.tokenizers, type) ) {

View file

@ -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, default: false,
ui: { ui: {
path: 'Chat > Appearance >> Community', 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.', 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' 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', { 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.polls', this.cleanHighlights, this);
this.chat.context.on('changed:chat.banners.prediction', 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.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); 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'), 'hype_train': this.chat.context.get('chat.banners.hype-train'),
'prediction': this.chat.context.get('chat.banners.prediction'), 'prediction': this.chat.context.get('chat.banners.prediction'),
'poll': this.chat.context.get('chat.banners.polls'), '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') '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; const type = entry.event.type;
if ( type && has(types, type) && ! types[type] ) { if ( type && has(types, type) && ! types[type] ) {
// Attempt to allow Golden Kappa hype trains? // Attempt to allow Golden Kappa hype trains?
if ( type === 'hype_train' && entry.event.typeDetails === '0' && this.chat.context.get('chat.banners.kappa-train') ) //if ( type === 'hype_train' && entry.event.typeDetails === '0' && this.chat.context.get('chat.banners.kappa-train') )
continue; // continue;
this.log.info('Removing community highlight: ', type, '#', entry.id); this.log.info('Removing community highlight: ', type, '#', entry.id);
this.community_dispatch({ this.community_dispatch({

View file

@ -7,15 +7,11 @@
import Module from 'utilities/module'; import Module from 'utilities/module';
import { findReactFragment } from 'utilities/dom'; 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 { TWITCH_POINTS_SETS, TWITCH_GLOBAL_SETS, TWITCH_PRIME_SETS, KNOWN_CODES, REPLACEMENTS, REPLACEMENT_BASE, KEYS } from 'utilities/constants';
import Twilight from 'site'; import Twilight from 'site';
const COMMAND_KEYS = [
'!',
'/'
];
// Prefer using these statically-allocated collators to String.localeCompare // Prefer using these statically-allocated collators to String.localeCompare
const locale = Intl.Collator(); const locale = Intl.Collator();
@ -75,7 +71,6 @@ export default class Input extends Module {
this.inject('site.fine'); this.inject('site.fine');
this.inject('site'); this.inject('site');
// Settings // Settings
this.settings.add('chat.hype.display-input', { this.settings.add('chat.hype.display-input', {
@ -783,7 +778,7 @@ export default class Input extends Module {
inst.getMatches = function(input, unknown, index) { inst.getMatches = function(input, unknown, index) {
try { try {
return index === 0 && COMMAND_KEYS.includes(input[0]) return index === 0 && t.chat.CommandPrefixes.includes(input[0])
? inst.getCommands(input) : null; ? inst.getCommands(input) : null;
} catch(err) { } catch(err) {
@ -797,11 +792,22 @@ export default class Input extends Module {
isEditor: inst.props.isCurrentUserEditor 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({ const event = t.makeEvent({
input, input,
permissionLevel: inst.props.permissionLevel, permissionLevel: inst.props.permissionLevel,
isEditor: inst.props.isCurrentUserEditor, 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); t.emit('chat:get-tab-commands', event);

View file

@ -616,17 +616,17 @@ export default class Directory extends Module {
// Are we getting a clip, a video, or a stream? // Are we getting a clip, a video, or a stream?
if ( props.slug ) { if ( props.slug ) {
// Clip // Clip
console.log('need flags for clip', props.slug); //console.log('need flags for clip', props.slug);
el._ffz_flags = []; el._ffz_flags = [];
} else if ( props.vodID ) { } else if ( props.vodID ) {
// Video // Video
console.log('need flags for vod', props.vodID); //console.log('need flags for vod', props.vodID);
el._ffz_flags = []; el._ffz_flags = [];
} else { } else {
// Stream? // 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 => { this.twitch_data.getStreamFlags(null, props.channelLogin).then(data => {
el._ffz_flags = data ?? []; el._ffz_flags = data ?? [];
this.updateCard(el); this.updateCard(el);
@ -670,12 +670,16 @@ export default class Directory extends Module {
} }
if ( ! should_blur ) { if ( ! should_blur ) {
const regexes = this.settings.get('__filter:directory.blur-titles'); const regexes = this.settings.get('__filter:directory.blur-titles');
if ( regexes && if ( regexes ) {
(( regexes[0] && regexes[0].test(props.title) ) || if ( regexes[0] )
( regexes[1] && regexes[1].test(props.title) )) 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; should_blur = true;
} }
}
el.classList.toggle('ffz-hide-thumbnail', should_blur); el.classList.toggle('ffz-hide-thumbnail', should_blur);
el.dataset.ffzType = props.streamType; el.dataset.ffzType = props.streamType;
@ -700,13 +704,17 @@ export default class Directory extends Module {
if ( ! should_hide ) { if ( ! should_hide ) {
const regexes = this.settings.get('__filter:directory.block-titles'); const regexes = this.settings.get('__filter:directory.block-titles');
if ( regexes && if ( regexes ) {
(( regexes[0] && regexes[0].test(props.title) ) || if ( regexes[0] )
( regexes[1] && regexes[1].test(props.title) )) 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; should_hide = true;
} }
} }
}
let hide_container = el.closest('.tw-tower > div'); let hide_container = el.closest('.tw-tower > div');
if ( ! hide_container ) if ( ! hide_container )

View file

@ -385,12 +385,15 @@ export default class Layout extends Module {
else { else {
const regexes = this.settings.get('__filter:directory.block-titles'); const regexes = this.settings.get('__filter:directory.block-titles');
const title = stream?.broadcaster?.broadcastSettings?.title; const title = stream?.broadcaster?.broadcastSettings?.title;
if ( regexes && title && if ( regexes && title ) {
(( regexes[0] && regexes[0].test(title) ) || if ( regexes[0] )
( regexes[1] && regexes[1].test(title) )) 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; should_hide = true;
} }
}
card.classList.toggle('ffz--side-nav-card-rerun', rerun); card.classList.toggle('ffz--side-nav-card-rerun', rerun);
card.classList.toggle('ffz--side-nav-card-offline', offline); card.classList.toggle('ffz--side-nav-card-offline', offline);