mirror of
https://github.com/FrankerFaceZ/FrankerFaceZ.git
synced 2025-08-02 16:08:31 +00:00
4.57.2
* API Added: Certain methods will now log warnings when in developer mode if being called incorrectly. For example, if an add-on registers new badge data without including its add-on ID as the data source. Expect these to be expanded over time. * API Added: Add-on modules and sub-modules will all have an `addon_id` property containing their source add-on's ID, as well as an `addon_root` property with a reference to the add-on's root module. * API Changed: The `ffz_user_class` property of chat messages should now be a `Set`, if it is defined. * API Changed: Add-on module proxies are now cached.
This commit is contained in:
parent
04969cc57e
commit
71f347ab70
12 changed files with 202 additions and 45 deletions
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "frankerfacez",
|
||||
"author": "Dan Salvato LLC",
|
||||
"version": "4.57.1",
|
||||
"version": "4.57.2",
|
||||
"description": "FrankerFaceZ is a Twitch enhancement suite.",
|
||||
"private": true,
|
||||
"license": "Apache-2.0",
|
||||
|
|
|
@ -346,6 +346,9 @@ export default class AddonManager extends Module {
|
|||
other[name] = null;
|
||||
}
|
||||
|
||||
// Send off a signal for other modules to unload related data.
|
||||
this.emit('addon:fully-unload', module.addon_id);
|
||||
|
||||
// Clean up the global reference.
|
||||
if ( this.__modules[module.__path] === module )
|
||||
delete this.__modules[module.__path]; /* = [
|
||||
|
@ -363,7 +366,7 @@ export default class AddonManager extends Module {
|
|||
|
||||
// Clean up all settings.
|
||||
for(const [key, def] of Array.from(this.settings.definitions.entries())) {
|
||||
if ( def && def.__source === module.__path ) {
|
||||
if ( def && def.__source === module.addon_id ) {
|
||||
this.settings.remove(key);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -395,6 +395,14 @@ export default class Badges extends Module {
|
|||
else
|
||||
store = badge.addon ? addon : ffz;
|
||||
|
||||
let name = badge.title;
|
||||
let extra;
|
||||
try {
|
||||
extra = maybe_call(badge.tooltipExtra, this, null, badge);
|
||||
} catch(err) { extra = null; }
|
||||
if ( extra && !(extra instanceof Promise) )
|
||||
name = name + extra;
|
||||
|
||||
const id = badge.base_id ?? key,
|
||||
is_this = id === key;
|
||||
let existing = addon_badges_by_id[id];
|
||||
|
@ -411,14 +419,14 @@ export default class Badges extends Module {
|
|||
|
||||
existing.versions.push({
|
||||
version: key,
|
||||
name: badge.title,
|
||||
name,
|
||||
color,
|
||||
image: image1x,
|
||||
styleImage: `url("${image1x}")`
|
||||
});
|
||||
|
||||
if ( is_this ) {
|
||||
existing.name = badge.title;
|
||||
existing.name = name;
|
||||
existing.color = color;
|
||||
existing.image = image;
|
||||
existing.styleImage = `url("${image}")`;
|
||||
|
@ -429,7 +437,7 @@ export default class Badges extends Module {
|
|||
id,
|
||||
key,
|
||||
provider: 'ffz',
|
||||
name: badge.title,
|
||||
name,
|
||||
color,
|
||||
image,
|
||||
image1x,
|
||||
|
@ -542,8 +550,9 @@ export default class Badges extends Module {
|
|||
</div>);
|
||||
|
||||
} else if ( p === 'ffz' ) {
|
||||
const badge = this.badges[d.id],
|
||||
extra = maybe_call(badge?.tooltipExtra, this, ds, d, target, tip);
|
||||
const full_badge = this.badges[d.id],
|
||||
badge = d.badge,
|
||||
extra = maybe_call(badge?.tooltipExtra ?? full_badge?.tooltipExtra, this, ds, badge, target, tip);
|
||||
|
||||
if ( extra instanceof Promise ) {
|
||||
promises = true;
|
||||
|
@ -582,33 +591,55 @@ export default class Badges extends Module {
|
|||
// Add-On Proxy
|
||||
// ========================================================================
|
||||
|
||||
getAddonProxy(module) {
|
||||
const path = module.__path;
|
||||
if ( ! path.startsWith('addon.') )
|
||||
getAddonProxy(addon_id, addon, module) {
|
||||
if ( ! addon_id )
|
||||
return this;
|
||||
|
||||
const addon_id = path.slice(6);
|
||||
const is_dev = addon?.dev ?? false;
|
||||
|
||||
const loadBadgeData = (badge_id, data, ...args) => {
|
||||
const overrides = {};
|
||||
|
||||
overrides.loadBadgeData = (badge_id, data, ...args) => {
|
||||
if ( data && data.addon === undefined )
|
||||
data.addon = addon_id;
|
||||
|
||||
return this.loadBadgeData(badge_id, data, ...args);
|
||||
};
|
||||
|
||||
const handler = {
|
||||
if ( is_dev ) {
|
||||
overrides.setBulk = (source, ...args) => {
|
||||
if ( ! source.includes(addon_id) )
|
||||
module.log.warn('[DEV-CHECK] Call to badges.setBulk did not include addon ID in source:', source);
|
||||
|
||||
return this.setBulk(source, ...args);
|
||||
};
|
||||
|
||||
overrides.deleteBulk = (source, ...args) => {
|
||||
if ( ! source.includes(addon_id) )
|
||||
module.log.warn('[DEV-CHECK] Call to badges.deleteBulk did not include addon ID in source:', source);
|
||||
|
||||
return this.deleteBulk(source, ...args);
|
||||
}
|
||||
|
||||
overrides.extendBulk = (source, ...args) => {
|
||||
if ( ! source.includes(addon_id) )
|
||||
module.log.warn('[DEV-CHECK] Call to badges.extendBulk did not include addon ID in source:', source);
|
||||
|
||||
return this.extendBulk(source, ...args);
|
||||
}
|
||||
}
|
||||
|
||||
return new Proxy(this, {
|
||||
get(obj, prop) {
|
||||
if ( prop === 'loadBadgeData' )
|
||||
return loadBadgeData;
|
||||
const thing = overrides[prop];
|
||||
if ( thing )
|
||||
return thing;
|
||||
return Reflect.get(...arguments);
|
||||
}
|
||||
};
|
||||
|
||||
return new Proxy(this, handler);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
getBadgeData(target) {
|
||||
let container = target.parentElement?.parentElement;
|
||||
if ( ! container?.dataset?.roomId )
|
||||
|
@ -860,6 +891,7 @@ export default class Badges extends Module {
|
|||
bd = {
|
||||
provider: 'ffz',
|
||||
id: badge.id,
|
||||
badge,
|
||||
image: bu[4] || bu[2] || bu[1],
|
||||
color: badge.color || full_badge.color,
|
||||
title: badge.title || full_badge.title,
|
||||
|
@ -1165,17 +1197,22 @@ export default class Badges extends Module {
|
|||
data.click_url = 'https://www.frankerfacez.com/subscribe';
|
||||
|
||||
if ( ! data.addon && (data.name === 'subwoofer') )
|
||||
data.tooltipExtra = async data => {
|
||||
const d = await this.getSubwooferMonths(data.user_id);
|
||||
if ( ! d?.months )
|
||||
return;
|
||||
data.tooltipExtra = data => {
|
||||
if ( ! data?.user_id )
|
||||
return null;
|
||||
|
||||
if ( d.lifetime )
|
||||
return '\n' + this.i18n.t('badges.subwoofer.lifetime', 'Lifetime Subwoofer');
|
||||
return this.getSubwooferMonths(data.user_id)
|
||||
.then(d => {
|
||||
if ( ! d?.months )
|
||||
return;
|
||||
|
||||
return '\n' + this.i18n.t('badges.subwoofer.months', '({count, plural, one {# Month} other {# Months}})', {
|
||||
count: d.months
|
||||
});
|
||||
if ( d.lifetime )
|
||||
return '\n' + this.i18n.t('badges.subwoofer.lifetime', 'Lifetime Subwoofer');
|
||||
|
||||
return '\n' + this.i18n.t('badges.subwoofer.months', '({count, plural, one {# Month} other {# Months}})', {
|
||||
count: d.months
|
||||
});
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -599,6 +599,54 @@ export default class Emotes extends Module {
|
|||
this.animLeave = this.animLeave.bind(this);
|
||||
}
|
||||
|
||||
|
||||
getAddonProxy(addon_id, addon, module) {
|
||||
if ( ! addon_id )
|
||||
return this;
|
||||
|
||||
const overrides = {};
|
||||
|
||||
if ( addon?.dev ) {
|
||||
overrides.addDefaultSet = (provider, ...args) => {
|
||||
if ( ! provider.includes(addon_id) )
|
||||
module.log.warn('[DEV-CHECK] Call to emotes.addDefaultSet did not include addon ID in provider:', provider);
|
||||
|
||||
return this.addDefaultSet(provider, ...args);
|
||||
}
|
||||
|
||||
overrides.removeDefaultSet = (provider, ...args) => {
|
||||
if ( ! provider.includes(addon_id) )
|
||||
module.log.warn('[DEV-CHECK] Call to emotes.removeDefaultSet did not include addon ID in provider:', provider);
|
||||
|
||||
return this.removeDefaultSet(provider, ...args);
|
||||
}
|
||||
|
||||
overrides.addSubSet = (provider, ...args) => {
|
||||
if ( ! provider.includes(addon_id) )
|
||||
module.log.warn('[DEV-CHECK] Call to emotes.addSubSet did not include addon ID in provider:', provider);
|
||||
|
||||
return this.addSubSet(provider, ...args);
|
||||
}
|
||||
|
||||
overrides.removeSubSet = (provider, ...args) => {
|
||||
if ( ! provider.includes(addon_id) )
|
||||
module.log.warn('[DEV-CHECK] Call to emotes.removeSubSet did not include addon ID in provider:', provider);
|
||||
|
||||
return this.removeSubSet(provider, ...args);
|
||||
}
|
||||
}
|
||||
|
||||
return new Proxy(this, {
|
||||
get(obj, prop) {
|
||||
const thing = overrides[prop];
|
||||
if ( thing )
|
||||
return thing;
|
||||
return Reflect.get(...arguments);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
onEnable() {
|
||||
this.style = new ManagedStyle('emotes');
|
||||
this.effect_style = new ManagedStyle('effects');
|
||||
|
|
|
@ -557,6 +557,31 @@ export default class Metadata extends Module {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
getAddonProxy(addon_id) {
|
||||
if ( ! addon_id )
|
||||
return this;
|
||||
|
||||
const overrides = {};
|
||||
|
||||
overrides.define = (key, definition) => {
|
||||
if ( definition )
|
||||
definition.__source = addon_id;
|
||||
|
||||
return this.define(key, definition);
|
||||
};
|
||||
|
||||
return new Proxy(this, {
|
||||
get(obj, prop) {
|
||||
const thing = overrides[prop];
|
||||
if ( thing )
|
||||
return thing;
|
||||
return Reflect.get(...arguments);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
onEnable() {
|
||||
const md = this.tooltips.types.metadata = target => {
|
||||
let el = target;
|
||||
|
@ -592,6 +617,19 @@ export default class Metadata extends Module {
|
|||
opts.modifiers.flip = {behavior: ['bottom','top']};
|
||||
return opts;
|
||||
}
|
||||
|
||||
this.on('addon:fully-unload', addon_id => {
|
||||
const removed = new Set;
|
||||
for(const [key,def] of Object.entries(this.definitions)) {
|
||||
if ( def?.__source === addon_id ) {
|
||||
removed.add(key);
|
||||
this.definitions[key] = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
if ( removed.size )
|
||||
this.updateMetadata([...removed]);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -997,22 +997,23 @@ export default class SettingsManager extends Module {
|
|||
// Add-On Proxy
|
||||
// ========================================================================
|
||||
|
||||
getAddonProxy(module) {
|
||||
const path = module.__path;
|
||||
getAddonProxy(addon_id) {
|
||||
if ( ! addon_id )
|
||||
return this;
|
||||
|
||||
const add = (key, definition) => {
|
||||
return this.add(key, definition, path);
|
||||
return this.add(key, definition, addon_id);
|
||||
}
|
||||
|
||||
const addUI = (key, definition) => {
|
||||
return this.addUI(key, definition, path);
|
||||
return this.addUI(key, definition, addon_id);
|
||||
}
|
||||
|
||||
const addClearable = (key, definition) => {
|
||||
return this.addClearable(key, definition, path);
|
||||
return this.addClearable(key, definition, addon_id);
|
||||
}
|
||||
|
||||
const handler = {
|
||||
return new Proxy(this, {
|
||||
get(obj, prop) {
|
||||
if ( prop === 'add' )
|
||||
return add;
|
||||
|
@ -1022,9 +1023,7 @@ export default class SettingsManager extends Module {
|
|||
return addClearable;
|
||||
return Reflect.get(...arguments);
|
||||
}
|
||||
}
|
||||
|
||||
return new Proxy(this, handler);
|
||||
});
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
|
|
|
@ -100,7 +100,9 @@ export default class Line extends Module {
|
|||
const override_name = t.overrides.getName(user.id);
|
||||
|
||||
let user_class = msg.ffz_user_class;
|
||||
if ( Array.isArray(user_class) )
|
||||
if ( user_class instanceof Set )
|
||||
user_class = [...user_class].join(' ');
|
||||
else if ( Array.isArray(user_class) )
|
||||
user_class = user_class.join(' ');
|
||||
|
||||
const user_props = {
|
||||
|
|
|
@ -1711,14 +1711,14 @@ export default class PlayerBase extends Module {
|
|||
clearTimeout(timer);
|
||||
ctx.removeEventListener('statechange', evt);
|
||||
if (ctx.state === 'suspended') {
|
||||
this.log.info('Aborting due to browser auto-play policy.');
|
||||
this.log.debug('Aborting due to browser auto-play policy.');
|
||||
return;
|
||||
}
|
||||
|
||||
this.createCompressor(inst, video, comp);
|
||||
}
|
||||
|
||||
this.log.info('Attempting to resume suspended AudioContext.');
|
||||
this.log.debug('Attempting to resume suspended AudioContext.');
|
||||
timer = setTimeout(evt, 100);
|
||||
try {
|
||||
ctx.addEventListener('statechange', evt);
|
||||
|
|
|
@ -969,7 +969,9 @@ other {# messages were deleted by a moderator.}
|
|||
override_name = t.overrides.getName(user.id);
|
||||
|
||||
let user_class = msg.ffz_user_class;
|
||||
if ( Array.isArray(user_class) )
|
||||
if ( user_class instanceof Set )
|
||||
user_class = [...user_class].join(' ');
|
||||
else if ( Array.isArray(user_class) )
|
||||
user_class = user_class.join(' ');
|
||||
|
||||
const user_props = {
|
||||
|
|
|
@ -280,7 +280,9 @@ export default class VideoChatHook extends Module {
|
|||
const override_name = t.overrides.getName(user.id);
|
||||
|
||||
let user_class = msg.ffz_user_class;
|
||||
if ( Array.isArray(user_class) )
|
||||
if ( user_class instanceof Set )
|
||||
user_class = [...user_class].join(' ');
|
||||
else if ( Array.isArray(user_class) )
|
||||
user_class = user_class.join(' ');
|
||||
|
||||
const user_props = {
|
||||
|
|
|
@ -1,9 +1,18 @@
|
|||
import Module from 'utilities/module';
|
||||
|
||||
const EXTRACTOR = /^addon\.([^.]+)(?:\.|$)/i;
|
||||
|
||||
function extractAddonId(path) {
|
||||
const match = EXTRACTOR.exec(path);
|
||||
if ( match )
|
||||
return match[1];
|
||||
}
|
||||
|
||||
export class Addon extends Module {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
|
||||
this.addon_id = extractAddonId(this.__path);
|
||||
this.addon_root = this;
|
||||
|
||||
this.inject('i18n');
|
||||
|
|
|
@ -37,7 +37,10 @@ export class Module extends EventEmitter {
|
|||
this.__modules = parent ? parent.__modules : {};
|
||||
this.children = {};
|
||||
|
||||
this.addon_root = parent ? parent.addon_root : null;
|
||||
if ( parent?.addon_id ) {
|
||||
this.addon_id = parent.addon_id;
|
||||
this.addon_root = parent.addon_root;
|
||||
}
|
||||
|
||||
if ( parent && ! parent.children[this.name] )
|
||||
parent.children[this.name] = this;
|
||||
|
@ -547,8 +550,22 @@ export class Module extends EventEmitter {
|
|||
|
||||
|
||||
__processModule(module, name) {
|
||||
if ( this.addon_root && module.getAddonProxy )
|
||||
return module.getAddonProxy(this.addon_root, this);
|
||||
if ( this.addon_root && module.getAddonProxy ) {
|
||||
const addon_id = this.addon_id;
|
||||
if ( ! module.__proxies )
|
||||
module.__proxies = {};
|
||||
|
||||
if ( module.__proxies[addon_id] )
|
||||
return module.__proxies[addon_id];
|
||||
|
||||
const addon = this.resolve('addons')?.getAddon?.(addon_id),
|
||||
out = module.getAddonProxy(addon_id, addon, this.addon_root, this);
|
||||
|
||||
if ( out !== module )
|
||||
module.__proxies[addon_id] = out;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
return module;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue