1
0
Fork 0
mirror of https://github.com/FrankerFaceZ/FrankerFaceZ.git synced 2025-08-06 06:10:54 +00:00
* Added: Add a `Reset` button to the headers of the badge visibility controls.
* Changed: Move some Twitch badges into a new `Twitch: Other` category.
* Changed: Do not display the badges in a category when the category is hidden to save space.
* Changed: Expose more data on emotes/emoji to tab completion.
* Fixed: Missing localization for certain items in Chat > Actions > Rooms.
* API Fixed: Ensure that emote set and badge IDs are cast to strings for consistent comparisons.
* API Fixed: Fix reference counting issues for emote sets when a set or badge is added from multiple providers.
* API Changed: Newly loaded emote sets are automatically scheduled for garbage collection if they have no users.
* API Changed: Added `removeAllSets(provider)` method to `Room`s and `Users`s.
* API Changed: Standardize `addSet(provider, set_id, data)` to allow passing set data in `Room` and `User`.
* API Changed: `addSet(...)` and `removeSet(...)` now return a boolean of whether or not the set was added or removed.
This commit is contained in:
SirStendec 2021-06-10 18:53:16 -04:00
parent a74faa95d3
commit 2a57ecb8a7
9 changed files with 188 additions and 59 deletions

View file

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

@ -294,6 +294,7 @@ export default class Badges extends Module {
getSettingsBadges(include_addons, callback) { getSettingsBadges(include_addons, callback) {
const twitch = [], const twitch = [],
other = [],
owl = [], owl = [],
tcon = [], tcon = [],
game = [], game = [],
@ -333,7 +334,9 @@ export default class Badges extends Module {
if ( v ) { if ( v ) {
let cat; let cat;
if ( badge.__cat === 'm-owl' ) if ( badge.__cat === 'm-other' )
cat = other;
else if ( badge.__cat === 'm-owl' )
cat = owl; cat = owl;
else if ( badge.__cat === 'm-tcon' ) else if ( badge.__cat === 'm-tcon' )
cat = tcon; cat = tcon;
@ -382,6 +385,7 @@ export default class Badges extends Module {
return [ return [
{title: 'Twitch', id: 'm-twitch', badges: twitch}, {title: 'Twitch', id: 'm-twitch', badges: twitch},
{title: 'Twitch: TwitchCon', id: 'm-tcon', badges: tcon}, {title: 'Twitch: TwitchCon', id: 'm-tcon', badges: tcon},
{title: 'Twitch: Other', id: 'm-other', badges: other},
{title: 'Twitch: Overwatch League', id: 'm-owl', badges: owl}, {title: 'Twitch: Overwatch League', id: 'm-owl', badges: owl},
{title: 'Twitch: Game', id: 'm-game', key: 'game', badges: game}, {title: 'Twitch: Game', id: 'm-game', key: 'game', badges: game},
{title: 'FrankerFaceZ', id: 'm-ffz', badges: ffz}, {title: 'FrankerFaceZ', id: 'm-ffz', badges: ffz},
@ -1148,10 +1152,22 @@ export default class Badges extends Module {
} }
const OTHER_BADGES = [
'vga-champ-2017',
'warcraft',
'samusoffer_beta',
'power-rangers',
'bits-charity',
'glhf-pledge'
];
export function getBadgeCategory(key) { export function getBadgeCategory(key) {
if ( key.startsWith('overwatch-league') ) if ( OTHER_BADGES.includes(key) )
return 'm-other';
else if ( key.startsWith('overwatch-league') )
return 'm-owl'; return 'm-owl';
else if ( key.startsWith('twitchcon') ) else if ( key.startsWith('twitchcon') || key.startsWith('glitchcon') )
return 'm-tcon'; return 'm-tcon';
else if ( /_\d+$/.test(key) ) else if ( /_\d+$/.test(key) )
return 'm-game'; return 'm-game';

View file

@ -100,6 +100,8 @@ export default class Emoji extends Module {
// For some reason, splitter is a function. // For some reason, splitter is a function.
this.splitter = splitter(); this.splitter = splitter();
this.categories = CATEGORIES;
this.emoji = {}; this.emoji = {};
this.names = {}; this.names = {};
this.chars = new Map; this.chars = new Map;

View file

@ -57,7 +57,6 @@ const MODIFIERS = {
}; };
export default class Emotes extends Module { export default class Emotes extends Module {
constructor(...args) { constructor(...args) {
super(...args); super(...args);
@ -636,39 +635,50 @@ export default class Emotes extends Module {
// ======================================================================== // ========================================================================
addDefaultSet(provider, set_id, data) { addDefaultSet(provider, set_id, data) {
let changed = false; if ( typeof set_id === 'number' )
set_id = `${set_id}`;
let changed = false, added = false;
if ( ! this.default_sets.sourceIncludes(provider, set_id) ) { if ( ! this.default_sets.sourceIncludes(provider, set_id) ) {
changed = ! this.default_sets.includes(set_id);
this.default_sets.push(provider, set_id); this.default_sets.push(provider, set_id);
this.refSet(set_id); added = true;
changed = true;
} }
if ( data ) if ( data )
this.loadSetData(set_id, data); this.loadSetData(set_id, data);
if ( changed ) if ( changed ) {
this.refSet(set_id);
this.emit(':update-default-sets', provider, set_id, true); this.emit(':update-default-sets', provider, set_id, true);
} }
removeDefaultSet(provider, set_id) { return added;
let changed = false;
if ( this.default_sets.sourceIncludes(provider, set_id) ) {
this.default_sets.remove(provider, set_id);
this.unrefSet(set_id);
changed = true;
} }
if ( changed ) removeDefaultSet(provider, set_id) {
if ( typeof set_id === 'number' )
set_id = `${set_id}`;
if ( this.default_sets.sourceIncludes(provider, set_id) ) {
this.default_sets.remove(provider, set_id);
if ( ! this.default_sets.includes(set_id) ) {
this.unrefSet(set_id);
this.emit(':update-default-sets', provider, set_id, false); this.emit(':update-default-sets', provider, set_id, false);
} }
return true;
}
return false;
}
refSet(set_id) { refSet(set_id) {
this._set_refs[set_id] = (this._set_refs[set_id] || 0) + 1; this._set_refs[set_id] = (this._set_refs[set_id] || 0) + 1;
if ( this._set_timers[set_id] ) { if ( this._set_timers[set_id] ) {
clearTimeout(this._set_timers[set_id]); clearTimeout(this._set_timers[set_id]);
this._set_timers[set_id] = null; this._set_timers[set_id] = null;
} }
} }
unrefSet(set_id) { unrefSet(set_id) {
@ -885,6 +895,11 @@ export default class Emotes extends Module {
this.log.info(`Loaded emote set #${set_id}: ${data.title} (${count} emotes)`); this.log.info(`Loaded emote set #${set_id}: ${data.title} (${count} emotes)`);
this.emit(':loaded', set_id, data); this.emit(':loaded', set_id, data);
// Don't let people endlessly load unused sets.
const refs = this._set_refs[set_id] || 0;
if ( refs <= 0 && ! this._set_timers[set_id] )
this._set_timers[set_id] = setTimeout(() => this.unloadSet(set_id), 5000);
} }
@ -904,6 +919,11 @@ export default class Emotes extends Module {
if ( ! suppress_log ) if ( ! suppress_log )
this.log.info(`Unloaded emote set #${set_id}: ${old_set.title}`); this.log.info(`Unloaded emote set #${set_id}: ${old_set.title}`);
if ( this._set_timers[set_id] ) {
clearTimeout(this._set_timers[set_id]);
this._set_timers[set_id] = null;
}
this.emit(':unloaded', set_id, old_set); this.emit(':unloaded', set_id, old_set);
this.emote_sets[set_id] = null; this.emote_sets[set_id] = null;
} }

View file

@ -306,13 +306,10 @@ export default class Room {
this.data = d; this.data = d;
if ( d.set ) { if ( d.set )
if ( ! this.emote_sets ) this.addSet('main', d.set);
this.emote_sets = new SourcedSet; else
this.emote_sets.set('main', d.set); this.removeAllSets('main');
} else if ( this.emote_sets )
this.emote_sets.delete('main');
if ( data.sets ) if ( data.sets )
for(const set_id in data.sets) for(const set_id in data.sets)
@ -349,29 +346,59 @@ export default class Room {
if ( ! this.emote_sets ) if ( ! this.emote_sets )
this.emote_sets = new SourcedSet; this.emote_sets = new SourcedSet;
let changed = false; if ( typeof set_id === 'number' )
set_id = `${set_id}`;
let changed = false, added = false;
if ( ! this.emote_sets.sourceIncludes(provider, set_id) ) { if ( ! this.emote_sets.sourceIncludes(provider, set_id) ) {
changed = ! this.emote_sets.includes(set_id);
this.emote_sets.push(provider, set_id); this.emote_sets.push(provider, set_id);
this.manager.emotes.refSet(set_id); added = true;
changed = true;
} }
if ( data ) if ( data )
this.manager.emotes.loadSetData(set_id, data); this.manager.emotes.loadSetData(set_id, data);
if ( changed ) if ( changed ) {
this.manager.emotes.refSet(set_id);
this.manager.emotes.emit(':update-room-sets', this, provider, set_id, true); this.manager.emotes.emit(':update-room-sets', this, provider, set_id, true);
} }
return added;
}
removeAllSets(provider) {
if ( this.destroyed || ! this.emote_sets )
return false;
const sets = this.emote_sets.get(provider);
if ( ! Array.isArray(sets) || ! sets.length )
return false;
for(const set_id of sets)
this.removeSet(provider, set_id);
return true;
}
removeSet(provider, set_id) { removeSet(provider, set_id) {
if ( this.destroyed || ! this.emote_sets ) if ( this.destroyed || ! this.emote_sets )
return; return;
if ( typeof set_id === 'number' )
set_id = `${set_id}`;
if ( this.emote_sets.sourceIncludes(provider, set_id) ) { if ( this.emote_sets.sourceIncludes(provider, set_id) ) {
this.emote_sets.remove(provider, set_id); this.emote_sets.remove(provider, set_id);
if ( ! this.emote_sets.includes(set_id) ) {
this.manager.emotes.unrefSet(set_id); this.manager.emotes.unrefSet(set_id);
this.manager.emotes.emit(':update-room-sets', this, provider, set_id, false); this.manager.emotes.emit(':update-room-sets', this, provider, set_id, false);
} }
return true;
}
return false;
} }

View file

@ -106,6 +106,9 @@ export default class User {
if ( this.destroyed ) if ( this.destroyed )
return; return;
if ( typeof badge_id === 'number' )
badge_id = `${badge_id}`;
if ( data ) if ( data )
data.id = badge_id; data.id = badge_id;
else else
@ -155,29 +158,64 @@ export default class User {
// Emote Sets // Emote Sets
// ======================================================================== // ========================================================================
addSet(provider, set_id) { addSet(provider, set_id, data) {
if ( this.destroyed ) if ( this.destroyed )
return; return;
if ( ! this.emote_sets ) if ( ! this.emote_sets )
this.emote_sets = new SourcedSet; this.emote_sets = new SourcedSet;
if ( typeof set_id === 'number' )
set_id = `${set_id}`;
let changed = false, added = false;
if ( ! this.emote_sets.sourceIncludes(provider, set_id) ) { if ( ! this.emote_sets.sourceIncludes(provider, set_id) ) {
changed = ! this.emote_sets.includes(set_id);
this.emote_sets.push(provider, set_id); this.emote_sets.push(provider, set_id);
added = true;
}
if ( data )
this.manager.emotes.loadSetData(set_id, data);
if ( changed ) {
this.manager.emotes.refSet(set_id); this.manager.emotes.refSet(set_id);
this.manager.emotes.emit(':update-user-sets', this, provider, set_id, true); this.manager.emotes.emit(':update-user-sets', this, provider, set_id, true);
return true;
} }
return added;
}
removeAllSets(provider) {
if ( this.destroyed || ! this.emote_sets )
return false;
const sets = this.emote_sets.get(provider);
if ( ! Array.isArray(sets) || ! sets.length )
return false;
for(const set_id of sets)
this.removeSet(provider, set_id);
return true;
} }
removeSet(provider, set_id) { removeSet(provider, set_id) {
if ( this.destroyed || ! this.emote_sets ) if ( this.destroyed || ! this.emote_sets )
return; return;
if ( typeof set_id === 'number' )
set_id = `${set_id}`;
if ( this.emote_sets.sourceIncludes(provider, set_id) ) { if ( this.emote_sets.sourceIncludes(provider, set_id) ) {
this.emote_sets.remove(provider, set_id); this.emote_sets.remove(provider, set_id);
if ( ! this.emote_sets.includes(set_id) ) {
this.manager.emotes.unrefSet(set_id); this.manager.emotes.unrefSet(set_id);
this.manager.emotes.emit(':update-user-sets', this, provider, set_id, false); this.manager.emotes.emit(':update-user-sets', this, provider, set_id, false);
} }
return true;
}
return false;
} }
} }

View file

@ -32,7 +32,7 @@
<header <header
v-if="sec.id" v-if="sec.id"
:class="{default: badgeDefault(sec.id)}" :class="{default: badgeDefault(sec.id)}"
class="tw-flex ffz-checkbox" class="tw-flex ffz-checkbox tw-align-items-center tw-z-above"
> >
<input <input
:id="sec.id" :id="sec.id"
@ -40,23 +40,33 @@
type="checkbox" type="checkbox"
class="ffz-checkbox__input" class="ffz-checkbox__input"
@click="onChange(sec.id, $event)" @click="onChange(sec.id, $event)"
@contextmenu.prevent="reset(sec.id)"
> >
<label <label
:for="sec.id" :for="sec.id"
:title="t('setting.right-click-reset', 'Right-Click to Reset')"
class="ffz-checkbox__label" class="ffz-checkbox__label"
@contextmenu.prevent="reset(sec.id)"
> >
<span class="tw-mg-l-1"> <span class="tw-mg-l-1">
{{ sec.title }} {{ sec.title }}
</span> </span>
</label> </label>
<div class="ffz--reset-button">
<button
v-if="! badgeDefault(sec.id)"
class="tw-mg-l-05 tw-button tw-button--text ffz-il-tooltip__container"
@click="reset(sec.id)"
>
<span class="tw-button__text ffz-i-cancel" />
<div class="ffz-il-tooltip ffz-il-tooltip--down ffz-il-tooltip--align-right">
{{ t('setting.reset', 'Reset to Default') }}
</div>
</button>
</div>
</header> </header>
<header v-else> <header v-else>
{{ sec.title }} {{ sec.title }}
</header> </header>
<ul class="tw-flex tw-flex-wrap tw-align-content-start"> <ul v-if="! sec.id || badgeChecked(sec.id)" class="tw-flex tw-flex-wrap tw-align-content-start">
<li <li
v-for="i in sec.badges" v-for="i in sec.badges"
:key="i.id" :key="i.id"
@ -96,7 +106,7 @@
class="tw-mg-t-05 tw-button ffz-button--hollow ffz-il-tooltip__container" class="tw-mg-t-05 tw-button ffz-button--hollow ffz-il-tooltip__container"
@click="reset(i.id)" @click="reset(i.id)"
> >
<span class="tw-button__text">Reset</span> <span class="tw-button__text ffz-i-cancel" />
<span class="ffz-il-tooltip ffz-il-tooltip--down ffz-il-tooltip--align-right"> <span class="ffz-il-tooltip ffz-il-tooltip--down ffz-il-tooltip--align-right">
{{ t('setting.reset', 'Reset to Default') }} {{ t('setting.reset', 'Reset to Default') }}
</span> </span>
@ -106,6 +116,11 @@
</label> </label>
</li> </li>
</ul> </ul>
<div v-else class="tw-c-text-alt-2 tw-font-size-4 tw-align-center tw-pd-05">
{{ t('setting.badges.hidden', '{count,plural,one{# badge is} other{# badges are} } hidden in this set', {
count: sec.badges.length
}) }}
</div>
</section> </section>
</div> </div>
</template> </template>

View file

@ -472,6 +472,7 @@ export default {
if ( ! this.item.inline ) { if ( ! this.item.inline ) {
out.push({ out.push({
title: 'New Line', title: 'New Line',
title_i18n: 'setting.new-line',
value: { value: {
v: {type: 'new-line'} v: {type: 'new-line'}
} }
@ -479,6 +480,7 @@ export default {
out.push({ out.push({
title: 'Space (Small)', title: 'Space (Small)',
title_i18n: 'setting.space-small',
value: { value: {
v: {type: 'space-small'} v: {type: 'space-small'}
} }
@ -486,6 +488,7 @@ export default {
out.push({ out.push({
title: 'Space (Expanding)', title: 'Space (Expanding)',
title_i18n: 'setting.space',
value: { value: {
v: {type: 'space'} v: {type: 'space'}
} }

View file

@ -588,17 +588,19 @@ export default class Input extends Module {
favorites = this.emotes.getFavorites('twitch'); favorites = this.emotes.getFavorites('twitch');
for(const set of emotes) { for(const set of emotes) {
if ( has_hidden ) {
const int_id = parseInt(set.id, 10), const int_id = parseInt(set.id, 10),
owner = set.owner, owner = set.owner,
is_points = TWITCH_POINTS_SETS.includes(int_id) || owner?.login === 'channel_points', is_points = TWITCH_POINTS_SETS.includes(int_id) || owner?.login === 'channel_points',
channel = is_points ? null : owner; channel = is_points ? null : owner;
let key = `twitch-set-${set.id}`; let key = `twitch-set-${set.id}`;
let extra = null;
if ( channel?.login ) if ( channel?.login ) {
key = `twitch-${channel.id}`; key = `twitch-${channel.id}`;
else if ( is_points ) extra = channel.displayName || channel.login;
} else if ( is_points )
key = 'twitch-points'; key = 'twitch-points';
else if ( TWITCH_GLOBAL_SETS.includes(int_id) ) else if ( TWITCH_GLOBAL_SETS.includes(int_id) )
key = 'twitch-global'; key = 'twitch-global';
@ -607,9 +609,8 @@ export default class Input extends Module {
else else
key = 'twitch-misc'; key = 'twitch-misc';
if ( hidden_sets.includes(key) ) if ( has_hidden && hidden_sets.includes(key) )
continue; continue;
}
for(const emote of set.emotes) { for(const emote of set.emotes) {
if ( ! emote || ! emote.id || hidden_emotes.includes(emote.id) ) if ( ! emote || ! emote.id || hidden_emotes.includes(emote.id) )
@ -635,6 +636,8 @@ export default class Input extends Module {
out.push({ out.push({
id, id,
source: key,
extra,
setID: set.id, setID: set.id,
token, token,
tokenLower: token.toLowerCase(), tokenLower: token.toLowerCase(),
@ -721,15 +724,17 @@ export default class Input extends Module {
included.add(source.raw); included.add(source.raw);
const srcSet = this.emoji.getFullImageSet(source.image, style); const srcSet = this.emoji.getFullImageSet(source.image, style);
const matched = `:${name}:`;
const favorite = favorites.includes(emoji.code); const favorite = favorites.includes(emoji.code);
results.push({ results.push({
current: input, current: input,
emoji: source, emoji: source,
matched,
srcSet, srcSet,
replacement: source.raw, replacement: source.raw,
element: inst.renderFFZEmojiSuggestion({ element: inst.renderFFZEmojiSuggestion({
token: `:${name}:`, token: matched,
id: `emoji-${emoji.code}`, id: `emoji-${emoji.code}`,
src: this.emoji.getFullImage(source.image, style), src: this.emoji.getFullImage(source.image, style),
srcSet, srcSet,
@ -760,6 +765,7 @@ export default class Input extends Module {
continue; continue;
const source = set.source || 'ffz', const source = set.source || 'ffz',
source_line = set.source_line || (`${set.source || 'FFZ'} ${set.title || 'Global'}`),
key = `${set.merge_source || source}-${set.merge_id || set.id}`; key = `${set.merge_source || source}-${set.merge_id || set.id}`;
if ( has_hidden && hidden_sets.includes(key) ) if ( has_hidden && hidden_sets.includes(key) )
@ -779,6 +785,8 @@ export default class Input extends Module {
out.push({ out.push({
id: `${source}-${emote.id}`, id: `${source}-${emote.id}`,
source,
extra: source_line,
token: emote.name, token: emote.name,
tokenLower: emote.name.toLowerCase(), tokenLower: emote.name.toLowerCase(),
srcSet: anim && emote.animSrcSet || emote.srcSet, srcSet: anim && emote.animSrcSet || emote.srcSet,