1
0
Fork 0
mirror of https://github.com/FrankerFaceZ/FrankerFaceZ.git synced 2025-08-02 16:08:31 +00:00
* Fixed: The icon not being hidden when hiding the native Twitch viewer count on channel pages.
* Fixed: Certain Twitch native code modules failing to load in the new React 18 build, causing issues with minor features failing to work.
* Fixed: The React button overlapping when using the setting to hide the unfollow button.
* Changed: Disable word wrapping within names when displaying a condensed list of users for a mass subscription.
* Experiments Changed: Added another experiment on how to host PubSub things.
This commit is contained in:
SirStendec 2024-03-01 01:48:50 -05:00
parent 909fdc25f2
commit 3aeb70f0fb
14 changed files with 143 additions and 44 deletions

View file

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

View file

@ -16,8 +16,16 @@
{"value": false, "weight": 0} {"value": false, "weight": 0}
] ]
}, },
"emqx_pubsub": {
"name": "EMQX MQTT-Based PubSub",
"description": "An experimental pubsub system running on an EMQX cluster, to see how that performs.",
"groups": [
{"value": true, "weight": 25},
{"value": false, "weight": 75}
]
},
"cf_pubsub": { "cf_pubsub": {
"name": "MQTT-Based PubSub", "name": "CF MQTT-Based PubSub",
"description": "An experimental new pubsub system that should be more reliable than the existing socket cluster.", "description": "An experimental new pubsub system that should be more reliable than the existing socket cluster.",
"groups": [ "groups": [
{"value": true, "weight": 0}, {"value": true, "weight": 0},

View file

@ -229,6 +229,7 @@ export default class ExperimentManager extends Module<'experiments', ExperimentE
this.cache = new Map; this.cache = new Map;
} }
getControlsLocked() { getControlsLocked() {
if ( DEBUG ) if ( DEBUG )
return false; return false;

View file

@ -104,7 +104,7 @@ export default class EmoteCard extends Module {
canReportTwitch() { canReportTwitch() {
const site = this.resolve('site'), const site = this.resolve('site'),
core = site.getCore?.(), //core = site.getCore?.(),
user = site.getUser(), user = site.getUser(),
web_munch = this.resolve('site.web_munch'); web_munch = this.resolve('site.web_munch');
@ -115,13 +115,13 @@ export default class EmoteCard extends Module {
return false; return false;
} }
return !! report_form && !! user?.id && core?.store?.dispatch; return !! report_form && !! user?.id && site?.store?.dispatch;
} }
reportTwitchEmote(id, channel) { reportTwitchEmote(id, channel) {
const site = this.resolve('site'), const site = this.resolve('site'),
core = site.getCore(), //core = site.getCore(),
user = site.getUser(), user = site.getUser(),
web_munch = this.resolve('site.web_munch'); web_munch = this.resolve('site.web_munch');
@ -132,10 +132,10 @@ export default class EmoteCard extends Module {
return false; return false;
} }
if ( ! user?.id || ! core?.store?.dispatch ) if ( ! user?.id || ! site?.store?.dispatch )
return false; return false;
core.store.dispatch({ site.store.dispatch({
type: 'core.modal.MODAL_SHOWN', type: 'core.modal.MODAL_SHOWN',
modalComponent: report_form, modalComponent: report_form,
modalProps: { modalProps: {

View file

@ -65,6 +65,8 @@ export default class PubSub extends Module<'pubsub', PubSubEvents> {
this.settings.add('pubsub.use-cluster', { this.settings.add('pubsub.use-cluster', {
default: () => { default: () => {
if ( this.experiments.getAssignment('emqx_pubsub') )
return 'EMQXTest';
if ( this.experiments.getAssignment('cf_pubsub') ) if ( this.experiments.getAssignment('cf_pubsub') )
return 'Staging'; return 'Staging';
return null; return null;

View file

@ -265,6 +265,12 @@ Twilight.KNOWN_MODULES = {
stack: n.fQ, stack: n.fQ,
dispatch: n.vJ dispatch: n.vJ
}; };
if ( has(n.fQ?._currentValue, 'highlights') && typeof n.vJ?._currentValue === 'function' )
return {
stack: n.fQ,
dispatch: n.vJ
};
} }
} }
@ -272,6 +278,7 @@ Twilight.KNOWN_MODULES = {
const VEND_CORE = n => ! n || n.includes('vendor') || n.includes('core'); const VEND_CORE = n => ! n || n.includes('vendor') || n.includes('core');
Twilight.KNOWN_MODULES.core.use_result = true; Twilight.KNOWN_MODULES.core.use_result = true;
Twilight.KNOWN_MODULES.core.skip_cache = true;
//Twilight.KNOWN_MODULES.core.chunks = 'core'; //Twilight.KNOWN_MODULES.core.chunks = 'core';
Twilight.KNOWN_MODULES.simplebar.chunks = VEND_CORE; Twilight.KNOWN_MODULES.simplebar.chunks = VEND_CORE;

View file

@ -334,6 +334,19 @@ export default class Channel extends Module {
} }
} }
if ( ! el.querySelector('ffz--native-viewers-container') ) {
let i = 0,
vel = el.querySelector('p[data-a-target="animated-channel-viewers-count"]');
while(vel && vel != el && i < 5) {
if ( vel.querySelector('svg') ) {
vel.classList.add('ffz--native-viewers-container');
break;
}
vel = vel.parentElement;
i++;
}
}
const react = this.fine.getReactInstance(el); const react = this.fine.getReactInstance(el);
let props = react?.child?.memoizedProps; let props = react?.child?.memoizedProps;
if ( ! props?.channelLogin ) if ( ! props?.channelLogin )

View file

@ -1,6 +1,7 @@
.ffz--meta-tray { .ffz--meta-tray {
.tw-svg svg[type="color-text-accessible-red"], .tw-svg svg[type="color-text-accessible-red"],
.ffz--native-viewers-container,
p[data-a-target="animated-channel-viewers-count"] { p[data-a-target="animated-channel-viewers-count"] {
display: none !important; display: none !important;
} }
} }

View file

@ -1,3 +1,7 @@
button[data-test-selector="unfollow-button"] { button[data-test-selector="unfollow-button"] {
display: none !important; display: none !important;
} }
div[data-target="channel-header-right"] > div {
margin-right: unset !important;
}

View file

@ -251,6 +251,7 @@
.ffz--giftee-name { .ffz--giftee-name {
cursor: pointer; cursor: pointer;
outline: none; outline: none;
word-break: keep-all;
&:hover { &:hover {
text-decoration: underline; text-decoration: underline;

View file

@ -16,7 +16,7 @@ const regex_cache = {};
function getRequireRegex(name) { function getRequireRegex(name) {
if ( ! regex_cache[name] ) if ( ! regex_cache[name] )
regex_cache[name] = new RegExp(`\\b${name}\\(([0-9e_+]+)\\)`, 'g'); regex_cache[name] = new RegExp(`\\b(?<!\\.)${name}\\(([0-9e_+]+)\\)`, 'g');
return regex_cache[name]; return regex_cache[name];
} }
@ -286,7 +286,7 @@ export default class WebMunch extends Module {
try { try {
const mod = this._require(id); const mod = this._require(id);
for(const key in mod) for(const key in mod)
if ( mod[key] && predicate(mod[key]) ) { if ( mod[key] && predicate(mod[key], mod, key) ) {
this.log.info(`Found in key "${key}" of module "${id}" (${this.chunkNameForModule(id)})`); this.log.info(`Found in key "${key}" of module "${id}" (${this.chunkNameForModule(id)})`);
if ( ! multi ) if ( ! multi )
return mod; return mod;
@ -683,4 +683,4 @@ export default class WebMunch extends Module {
} }
WebMunch.Requires = Requires; WebMunch.Requires = Requires;

View file

@ -307,9 +307,22 @@ export const LINK_DATA_HOSTS = {
export const PUBSUB_CLUSTERS = { export const PUBSUB_CLUSTERS = {
Production: `https://pubsub.frankerfacez.com`, Production: `https://pubsub.frankerfacez.com`,
Staging: `https://pubsub-staging-alt.frankerfacez.com`, Staging: `https://pubsub-staging-alt.frankerfacez.com`,
EMQXTest: 'emqx-test',
Development: `https://stendec.dev/ps/` Development: `https://stendec.dev/ps/`
} }
export const EMQX_SERVERS = [
//'catbag.frankerfacez.com',
//'pubsub-staging.frankerfacez.com',
'ayaya.frankerfacez.com',
'champ.frankerfacez.com',
'lilz.frankerfacez.com',
'pog.frankerfacez.com',
'yoohoo.frankerfacez.com',
'andknuckles.frankerfacez.com'
];
/** Whether or not we're running on macOS */ /** Whether or not we're running on macOS */
export const IS_OSX = navigator.platform ? navigator.platform.indexOf('Mac') !== -1 : /OS X/.test(navigator.userAgent); export const IS_OSX = navigator.platform ? navigator.platform.indexOf('Mac') !== -1 : /OS X/.test(navigator.userAgent);

View file

@ -266,7 +266,7 @@ var _Bytes = class _Bytes {
var CONNACK = 2; var CONNACK = 2;
function readConnack(reader, controlPacketFlags) { function readConnack(reader, controlPacketFlags) {
const { DEBUG } = Mqtt; const { DEBUG } = Mqtt;
checkEqual("controlPacketFlags", controlPacketFlags, 0); //checkEqual("controlPacketFlags", controlPacketFlags, 0);
const connectAcknowledgeFlags = reader.readUint8(); const connectAcknowledgeFlags = reader.readUint8();
const sessionPresent = (connectAcknowledgeFlags & 1) === 1; const sessionPresent = (connectAcknowledgeFlags & 1) === 1;
const connectAcknowledgeFlagsReserved = connectAcknowledgeFlags & 254; const connectAcknowledgeFlagsReserved = connectAcknowledgeFlags & 254;
@ -352,7 +352,7 @@ var _Bytes = class _Bytes {
var PUBLISH = 3; var PUBLISH = 3;
function readPublish(reader, controlPacketFlags) { function readPublish(reader, controlPacketFlags) {
const { DEBUG } = Mqtt; const { DEBUG } = Mqtt;
checkEqual("controlPacketFlags", controlPacketFlags, 0); //checkEqual("controlPacketFlags", controlPacketFlags, 0);
const dup = (controlPacketFlags & 8) === 8; const dup = (controlPacketFlags & 8) === 8;
const qosLevel = (controlPacketFlags & 6) >> 1; const qosLevel = (controlPacketFlags & 6) >> 1;
const retain = (controlPacketFlags & 1) === 1; const retain = (controlPacketFlags & 1) === 1;
@ -704,13 +704,13 @@ var _Bytes = class _Bytes {
} }
static async create(opts) { static async create(opts) {
const { DEBUG } = Mqtt; const { DEBUG } = Mqtt;
const { hostname, port } = opts; const { hostname, port, pathname } = opts;
if ("accept" in WebSocket.prototype) { if ("accept" in WebSocket.prototype) {
if (DEBUG) if (DEBUG)
console.log("Found WebSocket.accept, using Cloudflare workaround"); console.log("Found WebSocket.accept, using Cloudflare workaround");
if (port !== 443) if (port !== 443)
throw new Error(`Cloudflare Workers only support outgoing WebSocket requests on port 443 (https)`); throw new Error(`Cloudflare Workers only support outgoing WebSocket requests on port 443 (https)`);
const url2 = `https://${hostname}`; const url2 = `https://${hostname}${port ? `:${port}` : ''}${pathname ?? ''}`;
if (DEBUG) if (DEBUG)
console.log(`Fetching ${url2}`); console.log(`Fetching ${url2}`);
const resp = await fetch(url2, { headers: { upgrade: "websocket" } }); const resp = await fetch(url2, { headers: { upgrade: "websocket" } });
@ -724,7 +724,7 @@ var _Bytes = class _Bytes {
console.log("Accepted!"); console.log("Accepted!");
return new _WebSocketConnection(webSocket); return new _WebSocketConnection(webSocket);
} }
const url = `wss://${hostname}:${port}`; const url = `wss://${hostname}${port ? `:${port}` : ''}${pathname ?? ''}`;
const ws = new WebSocket(url, "mqtt"); const ws = new WebSocket(url, "mqtt");
if (DEBUG) if (DEBUG)
console.log(`new WebSocket('${url}', 'mqtt')`); console.log(`new WebSocket('${url}', 'mqtt')`);
@ -790,9 +790,10 @@ var _Bytes = class _Bytes {
this.receivedDisconnect = false; this.receivedDisconnect = false;
/** @internal */ /** @internal */
this.nextPacketId = 1; this.nextPacketId = 1;
const { hostname, port, protocol, maxMessagesPerSecond } = opts; const { hostname, port, pathname, protocol, maxMessagesPerSecond } = opts;
this.hostname = hostname; this.hostname = hostname;
this.port = port; this.port = port;
this.pathname = pathname;
this.protocol = protocol; this.protocol = protocol;
this.maxMessagesPerSecond = maxMessagesPerSecond; this.maxMessagesPerSecond = maxMessagesPerSecond;
} }
@ -837,9 +838,9 @@ var _Bytes = class _Bytes {
async connect(opts) { async connect(opts) {
const { DEBUG } = Mqtt; const { DEBUG } = Mqtt;
const { clientId = "", username, password, keepAlive = DEFAULT_KEEP_ALIVE_SECONDS, clean = false } = opts; const { clientId = "", username, password, keepAlive = DEFAULT_KEEP_ALIVE_SECONDS, clean = false } = opts;
const { protocol, hostname, port } = this; const { protocol, hostname, port, pathname } = this;
if (!this.connection) { if (!this.connection) {
this.connection = await _MqttClient.protocolHandlers[protocol]({ hostname, port }); this.connection = await _MqttClient.protocolHandlers[protocol]({ hostname, port, pathname });
this.connection.onRead = (bytes) => { this.connection.onRead = (bytes) => {
this.processBytes(bytes); this.processBytes(bytes);
}; };

View file

@ -2,6 +2,7 @@ import { EventEmitter } from "./events";
import { MqttClient, DISCONNECT } from './custom_denoflare_mqtt'; // "denoflare-mqtt"; import { MqttClient, DISCONNECT } from './custom_denoflare_mqtt'; // "denoflare-mqtt";
import { b64ToArrayBuffer, debounce, importRsaKey, make_enum, sleep } from "./object"; import { b64ToArrayBuffer, debounce, importRsaKey, make_enum, sleep } from "./object";
import { EMQX_SERVERS } from "./constants";
// Only match 1-4 digit numbers, to avoid matching twitch IDs. // Only match 1-4 digit numbers, to avoid matching twitch IDs.
// 9999 gives us millions of clients on a topic, so we're never // 9999 gives us millions of clients on a topic, so we're never
@ -112,30 +113,46 @@ export default class PubSubClient extends EventEmitter {
async _loadData() { async _loadData() {
let response, data; let response, data;
try {
// TODO: Send removed topics.
response = await fetch(this.server, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
id: this.id,
user: this.user ?? null,
topics: this.topics
})
});
if ( response.ok ) if ( this.server === 'emqx-test' ) {
data = await response.json(); // Hard-coded data.
const server = EMQX_SERVERS[Math.floor(Math.random() * EMQX_SERVERS.length)];
} catch(err) { data = {
throw new Error( require_signing: false,
'Unable to load PubSub data from server.', endpoint: `wss://${server}:8084/mqtt`,
{
cause: err username: 'anonymous',
} password: `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhbm9ueW1vdXMifQ.5DZP1bScMz4-MV_jGZveUKq4pFy9x_PJF9gSzAvj-wA`,
); topics: Object.fromEntries(this.topics.map(x => [x, 0]))
}
} else {
try {
// TODO: Send removed topics.
response = await fetch(this.server, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
id: this.id,
user: this.user ?? null,
topics: this.topics
})
});
if ( response.ok )
data = await response.json();
} catch(err) {
throw new Error(
'Unable to load PubSub data from server.',
{
cause: err
}
);
}
} }
if ( ! data?.endpoint ) if ( ! data?.endpoint )
@ -411,6 +428,7 @@ export default class PubSubClient extends EventEmitter {
const client = this._client = new MqttClient({ const client = this._client = new MqttClient({
hostname: url.hostname, hostname: url.hostname,
port: url.port ?? undefined, port: url.port ?? undefined,
pathname: url.pathname ?? undefined,
protocol: 'wss', protocol: 'wss',
maxMessagesPerSecond: 10 maxMessagesPerSecond: 10
}); });
@ -444,9 +462,13 @@ export default class PubSubClient extends EventEmitter {
return; return;
} }
let payload = message.payload;
if ( payload instanceof Uint8Array )
payload = new TextDecoder().decode(payload);
let msg; let msg;
try { try {
msg = JSON.parse(message.payload); msg = JSON.parse(payload);
} catch(err) { } catch(err) {
if ( this.logger ) if ( this.logger )
this.logger.warn(`Error decoding PubSub message on topic "${topic}":`, err); this.logger.warn(`Error decoding PubSub message on topic "${topic}":`, err);
@ -496,10 +518,12 @@ export default class PubSubClient extends EventEmitter {
return this._client.connect({ return this._client.connect({
clientId: data.client_id, clientId: data.client_id,
username: data.username,
password: data.password, password: data.password,
keepAlive: 120, keepAlive: 120,
clean: true clean: true
}).then(msg => { }).then(msg => {
this._conn_failures = 0;
this._state = State.Connected; this._state = State.Connected;
this.emit('connect', msg); this.emit('connect', msg);
@ -523,6 +547,30 @@ export default class PubSubClient extends EventEmitter {
}); });
return this._sendSubscribes() return this._sendSubscribes()
}).catch(err => {
if ( this.logger )
this.logger.debug('Error connecting to MQTT.', err);
disconnected = true;
this.emit('disconnect', null);
this._destroyClient();
if ( ! this._should_connect )
return;
this._conn_failures = (this._conn_failures || 0) + 1;
let delay = (this._conn_failures * Math.floor(Math.random() * 10) + 2) * 1000;
if ( delay > 60000 )
delay = (Math.floor(Math.random() * 60) + 30) * 1000;
if ( delay <= 2000 )
delay = 2000;
return sleep(delay).then(() => {
if ( this._should_connect && ! this._client )
this._createClient(data);
})
}); });
} }
@ -673,4 +721,4 @@ export default class PubSubClient extends EventEmitter {
}); });
} }
} }