diff --git a/package.json b/package.json
index 721c69c4..13767bb2 100755
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "frankerfacez",
"author": "Dan Salvato LLC",
- "version": "4.75.7",
+ "version": "4.76.0",
"description": "FrankerFaceZ is a Twitch enhancement suite.",
"private": true,
"license": "Apache-2.0",
diff --git a/src/addons.ts b/src/addons.ts
index 128b4129..6ed065db 100644
--- a/src/addons.ts
+++ b/src/addons.ts
@@ -346,7 +346,7 @@ export default class AddonManager extends Module<'addons'> {
}
getAddon(id: string) {
- const addon = this.addons[id];
+ const addon = this.addons[id] ?? null;
return Array.isArray(addon) ? null : addon;
}
diff --git a/src/entry_ext.js b/src/entry_ext.js
index bd59b44c..586f3f75 100644
--- a/src/entry_ext.js
+++ b/src/entry_ext.js
@@ -27,18 +27,67 @@
type: 'ffz_injecting'
});
- // Set up the extension message bridge.
- window.addEventListener('message', evt => {
- if (evt.source !== window)
+ // Set up a bridge for connections, since Firefox
+ // doesn't support externally_connectable.
+ const connections = new Map;
+
+ function handleConnect(id) {
+ if ( connections.has(id) )
return;
- if (evt.data && evt.data.type === 'ffz_to_ext')
- browser.runtime.sendMessage(evt.data.data, resp => {
- if (resp?.type === 'ffz_to_page')
- window.postMessage(resp.data, '*');
+ const port = browser.runtime.connect();
+ connections.set(id, port);
+
+ port.onMessage.addListener(msg => {
+ window.postMessage({
+ type: 'ffz-con-message',
+ id,
+ payload: msg
+ })
+ });
+
+ port.onDisconnect.addListener(() => {
+ connections.delete(id);
+ window.postMessage({
+ type: 'ffz-con-disconnect',
+ id
});
+ });
+ }
+
+ function handleDisconnect(id) {
+ const port = connections.get(id);
+ if ( port ) {
+ connections.delete(id);
+ port.disconnect();
+ }
+ }
+
+ function handleMessage(id, payload) {
+ const port = connection.get(id);
+ if ( port ) {
+ port.postMessage(payload);
+ }
+ }
+
+ window.addEventListener('message', evt => {
+ if (evt.source !== window || ! evt.data )
+ return;
+
+ const { type, id, payload } = evt.data;
+
+ if ( type === 'ffz-con-connect' )
+ handleConnect(id);
+
+ else if ( type === 'ffz-con-message' )
+ handleMessage(id, payload);
+
+ else if ( type === 'ffz-con-disconnect' )
+ handleDisconnect(id);
});
+
+ // Let the extension send messages to the page directly.
browser.runtime.onMessage.addListener((msg, sender) => {
if (msg?.type === 'ffz_to_page')
window.postMessage(msg.data, '*');
diff --git a/src/experiments.json b/src/experiments.json
index 248eb9e8..45ef6607 100644
--- a/src/experiments.json
+++ b/src/experiments.json
@@ -1,5 +1,6 @@
{
"api_load": {
+ "in_use": false,
"name": "New API Stress Testing",
"description": "Send duplicate requests to the new API server for load testing.",
"groups": [
@@ -8,25 +9,27 @@
]
},
"api_links": {
- "name": "API-Based Link Lookups",
- "description": "Use the new API to look up links instead of the socket cluster.",
+ "in_use": false,
"groups": [
- {"value": true, "weight": 0},
- {"value": "cf", "weight": 100},
- {"value": false, "weight": 0}
+ {"value": false, "weight": 100}
]
},
"emqx_pubsub": {
- "name": "EMQX MQTT-Based PubSub",
- "description": "An experimental pubsub system running on an EMQX cluster, to see how that performs.",
+ "in_use": false,
+ "groups": [
+ {"value": false, "weight": 100}
+ ]
+ },
+ "cf_pubsub": {
+ "in_use": false,
"groups": [
{"value": true, "weight": 0},
{"value": false, "weight": 100}
]
},
- "cf_pubsub": {
- "name": "CF MQTT-Based PubSub",
- "description": "An experimental new pubsub system that should be more reliable than the existing socket cluster.",
+ "worker_pubsub": {
+ "name": "Worker PubSub",
+ "description": "Whether or not to connect to the new Cloudflare Worker based PubSub system.",
"groups": [
{"value": true, "weight": 0},
{"value": false, "weight": 100}
diff --git a/src/load_tracker.ts b/src/load_tracker.ts
index 1edd73ed..430435be 100644
--- a/src/load_tracker.ts
+++ b/src/load_tracker.ts
@@ -93,8 +93,6 @@ export default class LoadTracker extends Module<'load_tracker', LoadEvents> {
/** @internal */
onEnable() {
- this.emit('load_tracker:schedule', 'test', 'fish');
-
this.on(':schedule', this.schedule);
}
diff --git a/src/modules/chat/emotes.js b/src/modules/chat/emotes.js
index 8056bf86..2e482459 100644
--- a/src/modules/chat/emotes.js
+++ b/src/modules/chat/emotes.js
@@ -7,7 +7,7 @@
import Module, { buildAddonProxy } from 'utilities/module';
import {ManagedStyle} from 'utilities/dom';
-import {get, has, timeout, SourcedSet, make_enum_flags, makeAddonIdChecker} from 'utilities/object';
+import {get, has, timeout, SourcedSet, make_enum_flags, makeAddonIdChecker, deep_copy} from 'utilities/object';
import {NEW_API, IS_OSX, EmoteTypes, TWITCH_GLOBAL_SETS, TWITCH_POINTS_SETS, TWITCH_PRIME_SETS, DEBUG} from 'utilities/constants';
import GET_EMOTE from './emote_info.gql';
@@ -463,16 +463,31 @@ export default class Emotes extends Module {
this.providers = new Map;
- this.providers.set('featured', {
- name: 'Featured',
- i18n_key: 'emote-menu.featured',
+ this.setProvider('ffz', {
+ name: 'FrankerFaceZ',
+ font_icon: 'ffz-i-zreknarf',
+ //icon: 'https://cdn.frankerfacez.com/badge/4/4/solid'
+ });
+
+ /*this.providers.set('ffz-featured', {
+ menu_name: 'Featured',
+ menu_i18n_key: 'emote-menu.featured',
sort_key: 75
- })
+ });*/
this.emote_sets = {};
this._set_refs = {};
this._set_timers = {};
+ this.settings.add('chat.emotes.source-priorities', {
+ default: null,
+ ui: {
+ path: 'Chat > Emote Priorities',
+ component: 'emote-priorities',
+ data: () => deep_copy(this.providers)
+ }
+ });
+
this.settings.add('chat.emotes.enabled', {
default: 2,
ui: {
@@ -631,9 +646,25 @@ export default class Emotes extends Module {
return this.addFilter(filter);
}
+ overrides.setProvider = (provider, data) => {
+ if ( is_dev && ! id_checker.test(provider) )
+ module.log.warn('[DEV-CHECK] Call to emotes.setProvider did not include addon ID in provider:', provider);
+
+ if ( data )
+ data.__source = addon_id;
+
+ return this.setProvider(provider, data);
+ }
+
overrides.addDefaultSet = (provider, set_id, data) => {
if ( is_dev && ! id_checker.test(provider) )
- module.log.warn('[DEV-CHECK] Call to emotes.addDefaultSet did not include addon ID in provider:', provider);
+ module.log.warn('[DEV-CHECK] Call to emotes.addDefaultSet did not include addon ID in provider:', provider);
+
+ if ( ! this.providers.has(provider) ) {
+ this.inferProvider(provider, addon_id);
+ if ( is_dev )
+ module.log.warn('[DEV-CHECK] Call to emotes.addDefaultSet for provider that has not been registered with emotes.setProvider:', provider);
+ }
if ( data ) {
if ( is_dev && ! id_checker.test(set_id) )
@@ -647,7 +678,13 @@ export default class Emotes extends Module {
overrides.addSubSet = (provider, set_id, data) => {
if ( is_dev && ! id_checker.test(provider) )
- module.log.warn('[DEV-CHECK] Call to emotes.addSubSet did not include addon ID in provider:', provider);
+ module.log.warn('[DEV-CHECK] Call to emotes.addSubSet did not include addon ID in provider:', provider);
+
+ if ( ! this.providers.has(provider) ) {
+ this.inferProvider(provider, addon_id);
+ if ( is_dev )
+ module.log.warn('[DEV-CHECK] Call to emotes.addSubSet for provider that has not been registered with emotes.setProvider:', provider);
+ }
if ( data ) {
if ( is_dev && ! id_checker.test(set_id) )
@@ -713,6 +750,8 @@ export default class Emotes extends Module {
// Generate the base filter CSS.
this.base_effect_css = generateBaseFilterCss();
+ this.parent.context.getChanges('chat.emotes.source-priorities', this.updatePriorities, this);
+
this.parent.context.on('changed:chat.effects.enable', this.updateEffects, this);
for(const input of EFFECT_STYLES)
if ( input.setting && ! Array.isArray(input.setting) )
@@ -807,6 +846,13 @@ export default class Emotes extends Module {
}
}
+ for(const [key, data] of this.providers.entries()) {
+ if ( data?.__source === addon_id ) {
+ removed++;
+ this.setProvider(key, null);
+ }
+ }
+
if ( removed ) {
this.log.debug(`Cleaned up ${removed} entries when unloading addon:`, addon_id);
// TODO: Debounced retokenize all chat messages.
@@ -817,6 +863,62 @@ export default class Emotes extends Module {
}
+ // ========================================================================
+ // Providers
+ // ========================================================================
+
+ updatePriorities(priorities) {
+ const l = priorities?.length;
+
+ if (! l || l <= 0)
+ this.sourceSortFn = null;
+ else
+ this.sourceSortFn = (first, second) => {
+ if (first.startsWith('ffz-'))
+ first = 'ffz';
+ if (second.startsWith('ffz-'))
+ second = 'ffz';
+
+ let first_priority = priorities.indexOf(first),
+ second_priority = priorities.indexOf(second);
+
+ if (first_priority === -1) first_priority = l;
+ if (second_priority === -1) second_priority = l;
+
+ return first_priority - second_priority;
+ };
+
+ // Update all existing sourced sets now.
+ this.default_sets.setSortFunction(this.sourceSortFn);
+
+ this.emit(':update-priorities', this.sourceSortFn);
+ }
+
+ inferProvider(provider, addon_id) {
+ if ( this.providers.has(provider) )
+ return;
+
+ const data = this.resolve('addons')?.getAddon(addon_id);
+ if ( data )
+ this.setProvider(provider, {
+ name: data.name,
+ i18n_key: data.name_i18n,
+ icon: data.icon,
+ description: provider,
+ __source: addon_id
+ });
+ }
+
+ setProvider(provider, data) {
+ if ( ! data )
+ this.providers.delete(provider);
+ else {
+ data.id = provider;
+ this.providers.set(provider, data);
+ }
+ }
+
+
// ========================================================================
// Emote Filtering
// ========================================================================
@@ -1183,17 +1285,17 @@ export default class Emotes extends Module {
emote_sets = room.emote_sets,
providers = emote_sets && emote_sets._sources;
- if ( providers && providers.has('featured') )
- for(const item of providers.get('featured')) {
+ if ( providers && providers.has('ffz-featured') )
+ for(const item of providers.get('ffz-featured')) {
const idx = new_sets.indexOf(item);
if ( idx === -1 )
- room.removeSet('featured', item);
+ room.removeSet('ffz-featured', item);
else
new_sets.splice(idx, 1);
}
for(const set_id of new_sets) {
- room.addSet('featured', set_id);
+ room.addSet('ffz-featured', set_id);
if ( ! this.emote_sets[set_id] )
this.loadSet(set_id);
diff --git a/src/modules/chat/index.js b/src/modules/chat/index.js
index 1598645d..d12bccb8 100644
--- a/src/modules/chat/index.js
+++ b/src/modules/chat/index.js
@@ -135,17 +135,7 @@ export default class Chat extends Module {
this.settings.add('debug.link-resolver.source', {
process: (ctx, val) => {
- if ( val == null ) {
- const exp = this.experiments.getAssignment('api_links');
- if ( exp === 'cf' )
- val = 'test-cf';
- else if ( exp )
- val = 'test';
- else
- val = 'socket';
- }
-
- return LINK_DATA_HOSTS[val] ?? LINK_DATA_HOSTS.test;
+ return LINK_DATA_HOSTS[val] ?? LINK_DATA_HOSTS.Production;
},
default: null,
@@ -1326,6 +1316,12 @@ export default class Chat extends Module {
if ( is_dev && ! id_checker.test(provider) )
module.log.warn('[DEV-CHECK] Call to getUser().addSet() did not include addon ID in provider:', provider);
+ if ( ! this.manager.emotes.providers.has(provider) ) {
+ this.manager.emotes.inferProvider(provider, addon_id);
+ if ( is_dev )
+ module.log.warn('[DEV-CHECK] Call to getUser().addSet() for provider that has not been registered with emotes.setProvider:', provider);
+ }
+
if ( data ) {
if ( is_dev && ! id_checker.test(set_id) )
module.log.warn('[DEV-CHECK] Call to getUser().addSet() loaded set data but did not include addon ID in set ID:', set_id);
@@ -1366,6 +1362,12 @@ export default class Chat extends Module {
if ( is_dev && ! id_checker.test(provider) )
module.log.warn('[DEV-CHECK] Call to getRoom().addSet() did not include addon ID in provider:', provider);
+ if ( ! this.manager.emotes.providers.has(provider) ) {
+ this.manager.emotes.inferProvider(provider, addon_id);
+ if ( is_dev )
+ module.log.warn('[DEV-CHECK] Call to getRoom().addSet() for provider that has not been registered with emotes.setProvider:', provider);
+ }
+
if ( data ) {
if ( is_dev && ! id_checker.test(set_id) )
module.log.warn('[DEV-CHECK] Call to getRoom().addSet() loaded set data but did not include addon ID in set ID:', set_id);
@@ -1524,6 +1526,12 @@ export default class Chat extends Module {
this.settings.provider.on('changed', this.onProviderChange, this);
this.on('site.subpump:pubsub-message', this.onPubSub, this);
+ this.on('chat.emotes:update-priorities', fn => {
+ for(const thing of this.iterateAllRoomsAndUsers()) {
+ if (thing.emote_sets)
+ thing.emote_sets.setSortFunction(fn);
+ }
+ });
if ( this.context.get('chat.filtering.need-colors') )
this.createColorCache().then(() => this.emit(':update-line-tokens'));
diff --git a/src/modules/chat/room.js b/src/modules/chat/room.js
index f15497e1..236f27fd 100644
--- a/src/modules/chat/room.js
+++ b/src/modules/chat/room.js
@@ -369,10 +369,10 @@ export default class Room {
this.data = d;
- this.removeAllSets('main');
+ this.removeAllSets('ffz-main');
if ( d.set )
- this.addSet('main', d.set);
+ this.addSet('ffz-main', d.set);
if ( data.sets )
for(const set_id in data.sets)
@@ -408,7 +408,7 @@ export default class Room {
return;
if ( ! this.emote_sets )
- this.emote_sets = new SourcedSet;
+ this.emote_sets = new SourcedSet(false, this.manager.emotes.sourceSortFn);
if ( typeof set_id === 'number' )
set_id = `${set_id}`;
diff --git a/src/modules/chat/user.ts b/src/modules/chat/user.ts
index 50cb962e..ea1cbc65 100644
--- a/src/modules/chat/user.ts
+++ b/src/modules/chat/user.ts
@@ -138,7 +138,7 @@ export default class User {
data = {id: badge_id};
if ( ! this.badges )
- this.badges = new SourcedSet;
+ this.badges = new SourcedSet(false, this.manager.emotes.sourceSortFn);
const existing = this.badges.get(provider);
if ( existing )
diff --git a/src/modules/emote_card/index.jsx b/src/modules/emote_card/index.jsx
index bc236f9f..86fb24fe 100644
--- a/src/modules/emote_card/index.jsx
+++ b/src/modules/emote_card/index.jsx
@@ -24,6 +24,8 @@ function getEmoteTypeFromTwitchType(type) {
return EmoteTypes.LimitedTime;
if ( type === 'BITS_BADGE_TIERS' )
return EmoteTypes.BitsTier;
+ if ( type === 'CHANNEL_POINTS' )
+ return EmoteTypes.ChannelPoints;
if ( type === 'TWO_FACTOR' )
return EmoteTypes.TwoFactor;
if ( type === 'PRIME' )
diff --git a/src/modules/main_menu/components/emote-priorities.vue b/src/modules/main_menu/components/emote-priorities.vue
new file mode 100644
index 00000000..58f78d3d
--- /dev/null
+++ b/src/modules/main_menu/components/emote-priorities.vue
@@ -0,0 +1,159 @@
+
+
+
+
+ {{ t('setting.warn-inheritence', 'These values are being overridden by another profile and may not take effect.') }}
+
+
+
+
{{ t('setting.priorities.about', 'Here, you can change the priorities of different emote providers. Please note that the provider priority (FFZ, etc.) is still secondary to the source priority (personal emotes > room emotes > global emotes).') }}
+
+
+
+
+ {{ t('setting.priorities.drag', 'Drag providers to change their priority.') }}
+
+
+
+
+
+
+ {{ t('setting.priorities.none', 'no priorities are defined in this profile') }}
+
+