1
0
Fork 0
mirror of https://github.com/FrankerFaceZ/FrankerFaceZ.git synced 2025-06-28 15:27:43 +00:00
* Added: Support for custom VIP Badge images.
* Fixed: Certain emotes breaking when `Large Emotes` is combined with `Fix bad Twitch emotes`. (Closes #1020)
* Fixed: Remember the Compressor state, if it was toggled, when resetting the player. (Closes #1024)
* API Added: The `chat` module has two methods for maintaining a list of possible message highlight reasons, for use populating UI. Accessible via methods `addHighlightReason(key, data)` and `getHighlightReasons()`.
* API Added: `basic_array_merge` setting type.
* API Added: Logs now include the initial URL that the script was loaded into.
* API Changed: `<select>` settings can now support multiple selected values.
This commit is contained in:
SirStendec 2021-04-14 16:53:15 -04:00
parent ae90b8e4fe
commit a80728a10d
13 changed files with 183 additions and 28 deletions

View file

@ -1,7 +1,7 @@
{ {
"name": "frankerfacez", "name": "frankerfacez",
"author": "Dan Salvato LLC", "author": "Dan Salvato LLC",
"version": "4.20.89", "version": "4.20.90",
"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

@ -34,7 +34,7 @@ class FFZBridge extends Module {
this.core_log = this.log.get('core'); this.core_log = this.log.get('core');
this.log.info(`FrankerFaceZ Settings Bridge v${VER} (build ${VER.build}${VER.commit ? ` - commit ${VER.commit}` : ''})`); this.log.info(`FrankerFaceZ Settings Bridge v${VER} (build ${VER.build}${VER.commit ? ` - commit ${VER.commit}` : ''}) (initial ${location})`);
// ======================================================================== // ========================================================================

View file

@ -43,7 +43,7 @@ class FrankerFaceZ extends Module {
this.core_log = this.log.get('core'); this.core_log = this.log.get('core');
this.log.info(`FrankerFaceZ Standalone Clips v${VER} (build ${VER.build}${VER.commit ? ` - commit ${VER.commit}` : ''})`); this.log.info(`FrankerFaceZ Standalone Clips v${VER} (build ${VER.build}${VER.commit ? ` - commit ${VER.commit}` : ''}) (initial ${location})`);
// ======================================================================== // ========================================================================

View file

@ -47,7 +47,7 @@ class FrankerFaceZ extends Module {
this.core_log = this.log.get('core'); this.core_log = this.log.get('core');
this.log.info(`FrankerFaceZ v${VER} (build ${VER.build}${VER.commit ? ` - commit ${VER.commit}` : ''})`); this.log.info(`FrankerFaceZ v${VER} (build ${VER.build}${VER.commit ? ` - commit ${VER.commit}` : ''}) (initial ${location})`);
// ======================================================================== // ========================================================================

View file

@ -257,7 +257,16 @@ export default class Badges extends Module {
title: 'Use custom moderator badges where available.', title: 'Use custom moderator badges where available.',
component: 'setting-check-box' component: 'setting-check-box'
} }
}) });
this.settings.add('chat.badges.custom-vip', {
default: true,
ui: {
path: 'Chat > Badges >> tabs ~> Appearance',
title: 'Use custom VIP badges where available.',
component: 'setting-check-box'
}
});
this.settings.add('chat.badges.style', { this.settings.add('chat.badges.style', {
default: 1, default: 1,
@ -558,6 +567,7 @@ export default class Badges extends Module {
const hidden_badges = skip_hide ? {} : (this.parent.context.get('chat.badges.hidden') || {}), const hidden_badges = skip_hide ? {} : (this.parent.context.get('chat.badges.hidden') || {}),
badge_style = this.parent.context.get('chat.badges.style'), badge_style = this.parent.context.get('chat.badges.style'),
custom_mod = this.parent.context.get('chat.badges.custom-mod'), custom_mod = this.parent.context.get('chat.badges.custom-mod'),
custom_vip = this.parent.context.get('chat.badges.custom-vip'),
is_mask = badge_style > 5, is_mask = badge_style > 5,
is_colored = badge_style !== 5, is_colored = badge_style !== 5,
has_image = badge_style !== 3 && badge_style !== 4, has_image = badge_style !== 3 && badge_style !== 4,
@ -599,19 +609,30 @@ export default class Badges extends Module {
slot = last_slot++; slot = last_slot++;
const data = dynamic_data[badge_id] || (badge_id === 'founder' && dynamic_data['subscriber']), const data = dynamic_data[badge_id] || (badge_id === 'founder' && dynamic_data['subscriber']),
urls = badge_id === 'moderator' && custom_mod && room && room.data && room.data.mod_urls, mod_urls = badge_id === 'moderator' && custom_mod && room && room.data && room.data.mod_urls,
vip_urls = badge_id === 'vip' && custom_vip && room && room.data && room.data.vip_badge,
badges = []; badges = [];
if ( urls ) { if ( mod_urls ) {
const bd = this.getTwitchBadge(badge_id, version, room_id, room_login); const bd = this.getTwitchBadge(badge_id, version, room_id, room_login);
badges.push({ badges.push({
provider: 'ffz', provider: 'ffz',
image: urls[4] || urls[2] || urls[1], image: mod_urls[4] || mod_urls[2] || mod_urls[1],
color: '#34ae0a', color: '#34ae0a',
title: bd ? bd.title : 'Moderator', title: bd ? bd.title : 'Moderator',
data data
}); });
} else if ( vip_urls ) {
const bd = this.getTwitchBadge(badge_id, version, room_id, room_login);
badges.push({
provider: 'ffz',
image: vip_urls[4] || vip_urls[2] || vip_urls[1],
color: 'transparent',
title: bd ? bd.title : 'VIP',
data
});
} else } else
badges.push({ badges.push({
provider: 'twitch', provider: 'twitch',

View file

@ -8,7 +8,7 @@ import dayjs from 'dayjs';
import Module from 'utilities/module'; import Module from 'utilities/module';
import {createElement, ManagedStyle} from 'utilities/dom'; import {createElement, ManagedStyle} from 'utilities/dom';
import {timeout, has, glob_to_regex, escape_regex, split_chars} from 'utilities/object'; import {timeout, has, glob_to_regex, escape_regex, split_chars, deep_copy} from 'utilities/object';
import Badges from './badges'; import Badges from './badges';
import Emotes from './emotes'; import Emotes from './emotes';
@ -88,11 +88,28 @@ export default class Chat extends Module {
this.rich_providers = {}; this.rich_providers = {};
this.__rich_providers = []; this.__rich_providers = [];
this._hl_reasons = {};
this.addHighlightReason('mention', 'Mentioned');
this.addHighlightReason('user', 'Highlight User');
this.addHighlightReason('badge', 'Highlight Badge');
this.addHighlightReason('term', 'Highlight Term');
// ======================================================================== // ========================================================================
// Settings // Settings
// ======================================================================== // ========================================================================
/*this.settings.add('debug.highlight-reason', {
default: [],
type: 'basic_array_merge',
ui: {
path: 'Chat > Debugging >> General',
title: 'Test',
component: 'setting-select-box',
multiple: true,
data: () => this.getHighlightReasons()
}
});*/
this.settings.add('debug.link-resolver.source', { this.settings.add('debug.link-resolver.source', {
default: null, default: null,
ui: { ui: {
@ -1598,6 +1615,28 @@ export default class Chat extends Module {
} }
addHighlightReason(key, data) {
if ( typeof key === 'object' && key.key ) {
data = key;
key = data.key;
} else if ( typeof data === 'string' )
data = {title: data};
data.value = data.key = key;
if ( ! data.i18n_key )
data.i18n_key = `hl-reason.${key}`;
if ( this._hl_reasons[key] )
throw new Error(`Highlight Reason already exists with key ${key}`);
this._hl_reasons[key] = data;
}
getHighlightReasons() {
return Object.values(this._hl_reasons);
}
addTokenizer(tokenizer) { addTokenizer(tokenizer) {
const type = tokenizer.type; const type = tokenizer.type;
this.tokenizers[type] = tokenizer; this.tokenizers[type] = tokenizer;

View file

@ -111,6 +111,7 @@ export default class Room {
this.style.delete('css'); this.style.delete('css');
this.buildModBadgeCSS(); this.buildModBadgeCSS();
this.buildVIPBadgeCSS();
} }
if ( other.badges && ! this.badges ) { if ( other.badges && ! this.badges ) {
@ -331,6 +332,7 @@ export default class Room {
this.style.delete('css'); this.style.delete('css');
this.buildModBadgeCSS(); this.buildModBadgeCSS();
this.buildVIPBadgeCSS();
return true; return true;
} }
@ -429,6 +431,28 @@ export default class Room {
this.buildBadgeCSS(); this.buildBadgeCSS();
} }
buildVIPBadgeCSS() {
if ( this.destroyed )
return;
if ( ! this.data || ! this.data.vip_badge || ! this.manager.context.get('chat.badges.custom-vip') )
return this.style.delete('vip-badge');
const urls = this.data.vip_badge,
image = `url("${urls[1]}")`;
let image_set;
if ( urls[2] || urls[4] )
image_set = `${WEBKIT}image-set(${image} 1x${urls[2] ? `, url("${urls[2]}") 2x` : ''}${urls[4] ? `, url("${urls[4]}") 4x` : ''})`;
this.style.set('vip-badge', `[data-room-id="${this.id}"] .ffz-badge[data-badge="vip"] {
background-color: transparent;
background-image: ${image};
${image_set ? `background-image: ${image_set};` : ''}
${WEBKIT}mask-image: unset;
}`);
}
buildModBadgeCSS() { buildModBadgeCSS() {
if ( this.destroyed ) if ( this.destroyed )
return; return;

View file

@ -1693,11 +1693,13 @@ export const TwitchEmotes = {
let src, srcSet; let src, srcSet;
let src2, srcSet2; let src2, srcSet2;
let can_big = true;
const replacement = REPLACEMENTS[e_id]; const replacement = REPLACEMENTS[e_id];
if ( replacement && use_replacements ) { if ( replacement && use_replacements ) {
src = `${REPLACEMENT_BASE}${replacement}`; src = `${REPLACEMENT_BASE}${replacement}`;
srcSet = ''; srcSet = '';
can_big = false;
} else { } else {
src = `${TWITCH_EMOTE_BASE}${e_id}/1.0`; src = `${TWITCH_EMOTE_BASE}${e_id}/1.0`;
@ -1718,7 +1720,8 @@ export const TwitchEmotes = {
src2, src2,
srcSet2, srcSet2,
big, big,
can_big: true, can_big,
height: 28, // Not always accurate but close enough.
text: text.slice(e_start - t_start, e_end - t_start).join(''), text: text.slice(e_start - t_start, e_end - t_start).join(''),
modifiers: [] modifiers: []
}); });

View file

@ -3,8 +3,8 @@
:class="{inherits: isInherited, default: isDefault}" :class="{inherits: isInherited, default: isDefault}"
class="ffz--widget ffz--select-box" class="ffz--widget ffz--select-box"
> >
<div class="tw-flex tw-align-items-center"> <div class="tw-flex tw-align-items-start">
<label :for="item.full_key"> <label :for="item.full_key" class="tw-mg-y-05">
{{ t(item.i18n_key, item.title) }} {{ t(item.i18n_key, item.title) }}
<span v-if="unseen" class="tw-pill">{{ t('setting.new', 'New') }}</span> <span v-if="unseen" class="tw-pill">{{ t('setting.new', 'New') }}</span>
</label> </label>
@ -12,6 +12,8 @@
<select <select
:id="item.full_key" :id="item.full_key"
ref="control" ref="control"
:multiple="item.multiple || false"
:size="item.size || 0"
class="tw-border-radius-medium tw-font-size-6 ffz-select tw-pd-l-1 tw-pd-r-3 tw-pd-y-05 tw-mg-05" class="tw-border-radius-medium tw-font-size-6 ffz-select tw-pd-l-1 tw-pd-r-3 tw-pd-y-05 tw-mg-05"
@change="onChange" @change="onChange"
> >
@ -25,7 +27,7 @@
<option <option
v-for="j in i.entries" v-for="j in i.entries"
:key="j.value" :key="j.value"
:selected="j.value === value" :selected="item.multiple ? (Array.isArray(value) && value.includes(j.value)) : j.value === value"
:value="j.v" :value="j.v"
> >
{{ j.i18n_key ? t(j.i18n_key, j.title, j) : j.title }} {{ j.i18n_key ? t(j.i18n_key, j.title, j) : j.title }}
@ -34,7 +36,7 @@
<option <option
v-else v-else
:key="i.value" :key="i.value"
:selected="i.value === value" :selected="item.multiple ? (Array.isArray(value) && value.includes(i.value)) : i.value === value"
:value="i.v" :value="i.v"
> >
{{ i.i18n_key ? t(i.i18n_key, i.title, i) : i.title }} {{ i.i18n_key ? t(i.i18n_key, i.title, i) : i.title }}
@ -119,6 +121,20 @@ export default {
methods: { methods: {
onChange() { onChange() {
if ( this.item.multiple ) {
const items = this.$refs.control.selectedOptions,
out = [];
for(let i=0; i < items.length; i++) {
const raw = this.data[items[i].index];
if ( raw )
out.push(raw.value);
}
this.set(out)
return;
}
const idx = this.$refs.control.value, const idx = this.$refs.control.value,
raw_value = this.data[idx]; raw_value = this.data[idx];

View file

@ -40,7 +40,7 @@ class FrankerFaceZ extends Module {
this.core_log = this.log.get('core'); this.core_log = this.log.get('core');
this.log.info(`FrankerFaceZ Standalone Player v${VER} (build ${VER.build}${VER.commit ? ` - commit ${VER.commit}` : ''})`); this.log.info(`FrankerFaceZ Standalone Player v${VER} (build ${VER.build}${VER.commit ? ` - commit ${VER.commit}` : ''}) (initial ${location})`);
// ======================================================================== // ========================================================================

View file

@ -44,6 +44,33 @@ export const object_merge = {
} }
export const basic_array_merge = {
get(key, profiles, log) {
const values = [],
sources = [];
for(const profile of profiles)
if ( profile.has(key) ) {
const val = profile.get(key);
if ( ! Array.isArray(val) ) {
log.warn(`Profile #${profile.id} has an invalid value for "${key}"`);
continue;
}
sources.push(profile.id);
for(const v of val)
values.push(v);
}
if ( sources.length )
return [
values,
sources
]
}
}
export const array_merge = { export const array_merge = {
default(val) { default(val) {
const values = []; const values = [];

View file

@ -597,16 +597,31 @@ export default class PlayerBase extends Module {
cls.prototype.ffzUpdateState = function() { cls.prototype.ffzUpdateState = function() {
this._ffz_state_raf = null; this._ffz_state_raf = null;
const cont = this.props.containerRef, const cont = this.props.containerRef;
player = this.props.mediaPlayerInstance;
if ( ! cont ) if ( ! cont )
return; return;
const ds = cont.dataset; const ds = cont.dataset;
ds.controls = this.state?.active || false; ds.controls = this.state?.active || false;
ds.ended = player?.state?.playerState === 'Ended'; let player = this.props.mediaPlayerInstance;
ds.paused = player?.state?.playerState === 'Idle'; if ( ! player )
return;
if ( player.core )
player = player.core;
const state = player.state?.state;
if ( state === 'Playing' ) {
const video = player.mediaSinkManager?.video;
if ( video?._ffz_maybe_compress ) {
video._ffz_maybe_compress = false;
t.compressPlayer(this);
}
}
ds.ended = state === 'Ended';
ds.paused = state === 'Idle';
} }
cls.prototype.ffzAttachListeners = function() { cls.prototype.ffzAttachListeners = function() {
@ -780,7 +795,7 @@ export default class PlayerBase extends Module {
} }
stopPlayer(player, events, inst) { stopPlayer(player, events, inst) {
if ( player && player.pause && (player.getPlayerState?.() || player.core?.getPlayerState?.()) === 'Playing' ) if ( player && player.pause && (player.getState?.() || player.core?.getState?.()) === 'Playing' )
player.pause(); player.pause();
else if ( events && ! events._ffz_stopping ) { else if ( events && ! events._ffz_stopping ) {
events._ffz_stopping = true; events._ffz_stopping = true;
@ -978,12 +993,14 @@ export default class PlayerBase extends Module {
if ( ! video || ! HAS_COMPRESSOR ) if ( ! video || ! HAS_COMPRESSOR )
return; return;
video._ffz_maybe_compress = false;
const compressed = video._ffz_compressed || false; const compressed = video._ffz_compressed || false;
let wanted = this.settings.get('player.compressor.default'); let wanted = video._ffz_toggled ? video._ffz_state : this.settings.get('player.compressor.default');
if ( e != null ) { if ( e != null ) {
e.preventDefault(); e.preventDefault();
video._ffz_toggled = true; video._ffz_toggled = true;
wanted = ! video._ffz_compressed; wanted = ! video._ffz_compressed;
video._ffz_state = wanted;
} }
if ( ! video._ffz_compressor ) { if ( ! video._ffz_compressor ) {
@ -1406,6 +1423,10 @@ export default class PlayerBase extends Module {
const new_vid = createElement('video'), const new_vid = createElement('video'),
vol = video?.volume ?? player.getVolume(), vol = video?.volume ?? player.getVolume(),
muted = player.isMuted(); muted = player.isMuted();
new_vid._ffz_state = video._ffz_state;
new_vid._ffz_toggled = video._ffz_toggled;
new_vid._ffz_maybe_compress = true;
new_vid.volume = muted ? 0 : vol; new_vid.volume = muted ? 0 : vol;
new_vid.playsInline = true; new_vid.playsInline = true;
this.installPlaybackRate(new_vid); this.installPlaybackRate(new_vid);

View file

@ -144,7 +144,7 @@
padding: .2rem 0 padding: .2rem 0
} }
.ffz-input, .ffz-select { .ffz-input, .ffz-select:not([multiple]) {
height: 3rem; height: 3rem;
} }
@ -160,22 +160,26 @@
-moz-appearance: none; -moz-appearance: none;
-webkit-appearance: none; -webkit-appearance: none;
appearance: none; appearance: none;
&:not([multiple]) {
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='%230e0e10' d='M10.5 13.683l2.85-2.442 1.3 1.518-3.337 2.86a1.25 1.25 0 01-1.626 0l-3.338-2.86 1.302-1.518 2.849 2.442zm0-7.366L7.65 8.76l-1.3-1.518 3.337-2.86a1.25 1.25 0 011.627 0l3.337 2.86-1.302 1.518L10.5 6.317z'/%3E%3C/svg%3E"); background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='%230e0e10' d='M10.5 13.683l2.85-2.442 1.3 1.518-3.337 2.86a1.25 1.25 0 01-1.626 0l-3.338-2.86 1.302-1.518 2.849 2.442zm0-7.366L7.65 8.76l-1.3-1.518 3.337-2.86a1.25 1.25 0 011.627 0l3.337 2.86-1.302 1.518L10.5 6.317z'/%3E%3C/svg%3E");
background-position: right .8rem center; background-position: right .8rem center;
background-repeat: no-repeat; background-repeat: no-repeat;
background-size: 2rem; background-size: 2rem;
}
border: var(--border-width-input) solid var(--color-border-input); border: var(--border-width-input) solid var(--color-border-input);
color: var(--color-text-input); color: var(--color-text-input);
cursor: pointer; cursor: pointer;
line-height: 1.5; line-height: 1.5;
line-height: normal; line-height: normal;
.tw-root--theme-dark & { .tw-root--theme-dark &:not([multiple]) {
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='%23efeff1' d='M10.5 13.683l2.85-2.442 1.3 1.518-3.337 2.86a1.25 1.25 0 01-1.626 0l-3.338-2.86 1.302-1.518 2.849 2.442zm0-7.366L7.65 8.76l-1.3-1.518 3.337-2.86a1.25 1.25 0 011.627 0l3.337 2.86-1.302 1.518L10.5 6.317z'/%3E%3C/svg%3E"); background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='%23efeff1' d='M10.5 13.683l2.85-2.442 1.3 1.518-3.337 2.86a1.25 1.25 0 01-1.626 0l-3.338-2.86 1.302-1.518 2.849 2.442zm0-7.366L7.65 8.76l-1.3-1.518 3.337-2.86a1.25 1.25 0 011.627 0l3.337 2.86-1.302 1.518L10.5 6.317z'/%3E%3C/svg%3E");
} }
// option isn't scoped, unlike default Twitch, to avoid flicker // option isn't scoped, unlike default Twitch, to avoid flicker
&[data-focus-visible-added], option { &[data-focus-visible-added], &:not([multiple]) option {
background-color: var(--color-background-input-focus); background-color: var(--color-background-input-focus);
border-color: var(--color-border-input-focus) border-color: var(--color-border-input-focus)
} }