mirror of
https://github.com/FrankerFaceZ/FrankerFaceZ.git
synced 2025-07-25 03:58:30 +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",
|
"name": "frankerfacez",
|
||||||
"author": "Dan Salvato LLC",
|
"author": "Dan Salvato LLC",
|
||||||
"version": "4.57.1",
|
"version": "4.57.2",
|
||||||
"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",
|
||||||
|
|
|
@ -346,6 +346,9 @@ export default class AddonManager extends Module {
|
||||||
other[name] = null;
|
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.
|
// Clean up the global reference.
|
||||||
if ( this.__modules[module.__path] === module )
|
if ( this.__modules[module.__path] === module )
|
||||||
delete this.__modules[module.__path]; /* = [
|
delete this.__modules[module.__path]; /* = [
|
||||||
|
@ -363,7 +366,7 @@ export default class AddonManager extends Module {
|
||||||
|
|
||||||
// Clean up all settings.
|
// Clean up all settings.
|
||||||
for(const [key, def] of Array.from(this.settings.definitions.entries())) {
|
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);
|
this.settings.remove(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -395,6 +395,14 @@ export default class Badges extends Module {
|
||||||
else
|
else
|
||||||
store = badge.addon ? addon : ffz;
|
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,
|
const id = badge.base_id ?? key,
|
||||||
is_this = id === key;
|
is_this = id === key;
|
||||||
let existing = addon_badges_by_id[id];
|
let existing = addon_badges_by_id[id];
|
||||||
|
@ -411,14 +419,14 @@ export default class Badges extends Module {
|
||||||
|
|
||||||
existing.versions.push({
|
existing.versions.push({
|
||||||
version: key,
|
version: key,
|
||||||
name: badge.title,
|
name,
|
||||||
color,
|
color,
|
||||||
image: image1x,
|
image: image1x,
|
||||||
styleImage: `url("${image1x}")`
|
styleImage: `url("${image1x}")`
|
||||||
});
|
});
|
||||||
|
|
||||||
if ( is_this ) {
|
if ( is_this ) {
|
||||||
existing.name = badge.title;
|
existing.name = name;
|
||||||
existing.color = color;
|
existing.color = color;
|
||||||
existing.image = image;
|
existing.image = image;
|
||||||
existing.styleImage = `url("${image}")`;
|
existing.styleImage = `url("${image}")`;
|
||||||
|
@ -429,7 +437,7 @@ export default class Badges extends Module {
|
||||||
id,
|
id,
|
||||||
key,
|
key,
|
||||||
provider: 'ffz',
|
provider: 'ffz',
|
||||||
name: badge.title,
|
name,
|
||||||
color,
|
color,
|
||||||
image,
|
image,
|
||||||
image1x,
|
image1x,
|
||||||
|
@ -542,8 +550,9 @@ export default class Badges extends Module {
|
||||||
</div>);
|
</div>);
|
||||||
|
|
||||||
} else if ( p === 'ffz' ) {
|
} else if ( p === 'ffz' ) {
|
||||||
const badge = this.badges[d.id],
|
const full_badge = this.badges[d.id],
|
||||||
extra = maybe_call(badge?.tooltipExtra, this, ds, d, target, tip);
|
badge = d.badge,
|
||||||
|
extra = maybe_call(badge?.tooltipExtra ?? full_badge?.tooltipExtra, this, ds, badge, target, tip);
|
||||||
|
|
||||||
if ( extra instanceof Promise ) {
|
if ( extra instanceof Promise ) {
|
||||||
promises = true;
|
promises = true;
|
||||||
|
@ -582,33 +591,55 @@ export default class Badges extends Module {
|
||||||
// Add-On Proxy
|
// Add-On Proxy
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
|
|
||||||
getAddonProxy(module) {
|
getAddonProxy(addon_id, addon, module) {
|
||||||
const path = module.__path;
|
if ( ! addon_id )
|
||||||
if ( ! path.startsWith('addon.') )
|
|
||||||
return this;
|
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 )
|
if ( data && data.addon === undefined )
|
||||||
data.addon = addon_id;
|
data.addon = addon_id;
|
||||||
|
|
||||||
return this.loadBadgeData(badge_id, data, ...args);
|
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) {
|
get(obj, prop) {
|
||||||
if ( prop === 'loadBadgeData' )
|
const thing = overrides[prop];
|
||||||
return loadBadgeData;
|
if ( thing )
|
||||||
|
return thing;
|
||||||
return Reflect.get(...arguments);
|
return Reflect.get(...arguments);
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
|
|
||||||
return new Proxy(this, handler);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
getBadgeData(target) {
|
getBadgeData(target) {
|
||||||
let container = target.parentElement?.parentElement;
|
let container = target.parentElement?.parentElement;
|
||||||
if ( ! container?.dataset?.roomId )
|
if ( ! container?.dataset?.roomId )
|
||||||
|
@ -860,6 +891,7 @@ export default class Badges extends Module {
|
||||||
bd = {
|
bd = {
|
||||||
provider: 'ffz',
|
provider: 'ffz',
|
||||||
id: badge.id,
|
id: badge.id,
|
||||||
|
badge,
|
||||||
image: bu[4] || bu[2] || bu[1],
|
image: bu[4] || bu[2] || bu[1],
|
||||||
color: badge.color || full_badge.color,
|
color: badge.color || full_badge.color,
|
||||||
title: badge.title || full_badge.title,
|
title: badge.title || full_badge.title,
|
||||||
|
@ -1165,17 +1197,22 @@ export default class Badges extends Module {
|
||||||
data.click_url = 'https://www.frankerfacez.com/subscribe';
|
data.click_url = 'https://www.frankerfacez.com/subscribe';
|
||||||
|
|
||||||
if ( ! data.addon && (data.name === 'subwoofer') )
|
if ( ! data.addon && (data.name === 'subwoofer') )
|
||||||
data.tooltipExtra = async data => {
|
data.tooltipExtra = data => {
|
||||||
const d = await this.getSubwooferMonths(data.user_id);
|
if ( ! data?.user_id )
|
||||||
if ( ! d?.months )
|
return null;
|
||||||
return;
|
|
||||||
|
|
||||||
if ( d.lifetime )
|
return this.getSubwooferMonths(data.user_id)
|
||||||
return '\n' + this.i18n.t('badges.subwoofer.lifetime', 'Lifetime Subwoofer');
|
.then(d => {
|
||||||
|
if ( ! d?.months )
|
||||||
|
return;
|
||||||
|
|
||||||
return '\n' + this.i18n.t('badges.subwoofer.months', '({count, plural, one {# Month} other {# Months}})', {
|
if ( d.lifetime )
|
||||||
count: d.months
|
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);
|
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() {
|
onEnable() {
|
||||||
this.style = new ManagedStyle('emotes');
|
this.style = new ManagedStyle('emotes');
|
||||||
this.effect_style = new ManagedStyle('effects');
|
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() {
|
onEnable() {
|
||||||
const md = this.tooltips.types.metadata = target => {
|
const md = this.tooltips.types.metadata = target => {
|
||||||
let el = target;
|
let el = target;
|
||||||
|
@ -592,6 +617,19 @@ export default class Metadata extends Module {
|
||||||
opts.modifiers.flip = {behavior: ['bottom','top']};
|
opts.modifiers.flip = {behavior: ['bottom','top']};
|
||||||
return opts;
|
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
|
// Add-On Proxy
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
|
|
||||||
getAddonProxy(module) {
|
getAddonProxy(addon_id) {
|
||||||
const path = module.__path;
|
if ( ! addon_id )
|
||||||
|
return this;
|
||||||
|
|
||||||
const add = (key, definition) => {
|
const add = (key, definition) => {
|
||||||
return this.add(key, definition, path);
|
return this.add(key, definition, addon_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
const addUI = (key, definition) => {
|
const addUI = (key, definition) => {
|
||||||
return this.addUI(key, definition, path);
|
return this.addUI(key, definition, addon_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
const addClearable = (key, definition) => {
|
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) {
|
get(obj, prop) {
|
||||||
if ( prop === 'add' )
|
if ( prop === 'add' )
|
||||||
return add;
|
return add;
|
||||||
|
@ -1022,9 +1023,7 @@ export default class SettingsManager extends Module {
|
||||||
return addClearable;
|
return addClearable;
|
||||||
return Reflect.get(...arguments);
|
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);
|
const override_name = t.overrides.getName(user.id);
|
||||||
|
|
||||||
let user_class = msg.ffz_user_class;
|
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(' ');
|
user_class = user_class.join(' ');
|
||||||
|
|
||||||
const user_props = {
|
const user_props = {
|
||||||
|
|
|
@ -1711,14 +1711,14 @@ export default class PlayerBase extends Module {
|
||||||
clearTimeout(timer);
|
clearTimeout(timer);
|
||||||
ctx.removeEventListener('statechange', evt);
|
ctx.removeEventListener('statechange', evt);
|
||||||
if (ctx.state === 'suspended') {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.createCompressor(inst, video, comp);
|
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);
|
timer = setTimeout(evt, 100);
|
||||||
try {
|
try {
|
||||||
ctx.addEventListener('statechange', evt);
|
ctx.addEventListener('statechange', evt);
|
||||||
|
|
|
@ -969,7 +969,9 @@ other {# messages were deleted by a moderator.}
|
||||||
override_name = t.overrides.getName(user.id);
|
override_name = t.overrides.getName(user.id);
|
||||||
|
|
||||||
let user_class = msg.ffz_user_class;
|
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(' ');
|
user_class = user_class.join(' ');
|
||||||
|
|
||||||
const user_props = {
|
const user_props = {
|
||||||
|
|
|
@ -280,7 +280,9 @@ export default class VideoChatHook extends Module {
|
||||||
const override_name = t.overrides.getName(user.id);
|
const override_name = t.overrides.getName(user.id);
|
||||||
|
|
||||||
let user_class = msg.ffz_user_class;
|
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(' ');
|
user_class = user_class.join(' ');
|
||||||
|
|
||||||
const user_props = {
|
const user_props = {
|
||||||
|
|
|
@ -1,9 +1,18 @@
|
||||||
import Module from 'utilities/module';
|
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 {
|
export class Addon extends Module {
|
||||||
constructor(...args) {
|
constructor(...args) {
|
||||||
super(...args);
|
super(...args);
|
||||||
|
|
||||||
|
this.addon_id = extractAddonId(this.__path);
|
||||||
this.addon_root = this;
|
this.addon_root = this;
|
||||||
|
|
||||||
this.inject('i18n');
|
this.inject('i18n');
|
||||||
|
|
|
@ -37,7 +37,10 @@ export class Module extends EventEmitter {
|
||||||
this.__modules = parent ? parent.__modules : {};
|
this.__modules = parent ? parent.__modules : {};
|
||||||
this.children = {};
|
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] )
|
if ( parent && ! parent.children[this.name] )
|
||||||
parent.children[this.name] = this;
|
parent.children[this.name] = this;
|
||||||
|
@ -547,8 +550,22 @@ export class Module extends EventEmitter {
|
||||||
|
|
||||||
|
|
||||||
__processModule(module, name) {
|
__processModule(module, name) {
|
||||||
if ( this.addon_root && module.getAddonProxy )
|
if ( this.addon_root && module.getAddonProxy ) {
|
||||||
return module.getAddonProxy(this.addon_root, this);
|
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;
|
return module;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue