1
0
Fork 0
mirror of https://github.com/FrankerFaceZ/FrankerFaceZ.git synced 2025-06-28 15:27:43 +00:00
* Fixed: The `/ffz reload` command is now `/ffz:reload` to remove an undesirable behavior with Twitch's completion handling when backspacing.
* Fixed: Spaces being included in links when they shouldn't be.
* Fixed: Previews of emotes in chat when typing their names directly.
* Changed: Initial work on tracking the audio/video de-sync when using audio APIs for the compressor. This appears as a value in the stream latency metadata tool-tip, but currently drifts whenever the player is paused.
* Changed: Initial work on allowing the extension to be loaded from a bundled extension.
* API Changed: The load tracker now returns a list of reported loading keys when firing events.
This commit is contained in:
SirStendec 2023-05-19 15:02:25 -04:00
parent 67c64e6f7e
commit 109898a10d
13 changed files with 120 additions and 35 deletions

View file

@ -34,6 +34,7 @@ module.exports = {
'__version_minor__': false, '__version_minor__': false,
'__version_patch__': false, '__version_patch__': false,
'__version_prerelease__': false, '__version_prerelease__': false,
'__extension__': false,
'FrankerFaceZ': false 'FrankerFaceZ': false
}, },
'rules': { 'rules': {

View file

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

@ -5,7 +5,7 @@
// ============================================================================ // ============================================================================
import Module from 'utilities/module'; import Module from 'utilities/module';
import { SERVER } from 'utilities/constants'; import { EXTENSION, SERVER_OR_EXT } from 'utilities/constants';
import { createElement } from 'utilities/dom'; import { createElement } from 'utilities/dom';
import { timeout, has } from 'utilities/object'; import { timeout, has } from 'utilities/object';
import { getBuster } from 'utilities/time'; import { getBuster } from 'utilities/time';
@ -69,6 +69,7 @@ export default class AddonManager extends Module {
off: (...args) => this.off(...args) off: (...args) => this.off(...args)
}); });
if ( ! EXTENSION )
this.settings.add('addons.dev.server', { this.settings.add('addons.dev.server', {
default: false, default: false,
ui: { ui: {
@ -151,9 +152,13 @@ export default class AddonManager extends Module {
async loadAddonData() { async loadAddonData() {
const [cdn_data, local_data] = await Promise.all([ const [cdn_data, local_data] = await Promise.all([
fetchJSON(`${SERVER}/script/addons.json?_=${getBuster(30)}`), fetchJSON(`${SERVER_OR_EXT}/addons.json?_=${getBuster(30)}`),
this.settings.get('addons.dev.server') ?
fetchJSON(`https://localhost:8001/script/addons.json?_=${getBuster()}`) : null // Do not attempt to load local add-ons if using the extension, as
// loading external code is against the policy of basically everyone.
(! EXTENSION && this.settings.get('addons.dev.server'))
? fetchJSON(`https://localhost:8001/script/addons.json?_=${getBuster()}`)
: null
]); ]);
if ( Array.isArray(cdn_data) ) if ( Array.isArray(cdn_data) )
@ -469,7 +474,7 @@ export default class AddonManager extends Module {
document.head.appendChild(createElement('script', { document.head.appendChild(createElement('script', {
id: `ffz-loaded-addon-${addon.id}`, id: `ffz-loaded-addon-${addon.id}`,
type: 'text/javascript', type: 'text/javascript',
src: addon.src || `${addon.dev ? 'https://localhost:8001' : SERVER}/script/addons/${addon.id}/script.js?_=${getBuster(30)}`, src: addon.src || `${addon.dev ? 'https://localhost:8001/script' : SERVER_OR_EXT}/addons/${addon.id}/script.js?_=${getBuster(30)}`,
crossorigin: 'anonymous' crossorigin: 'anonymous'
})); }));

25
src/entry_ext.js Normal file
View file

@ -0,0 +1,25 @@
/* eslint strict: off */
'use strict';
(() => {
// Don't run on certain sub-domains.
if ( /^(?:localhost\.rig|blog|im|chatdepot|tmi|api|brand|dev|gql|passport)\./.test(location.hostname) )
return;
const HOST = location.hostname,
SERVER = __EXTENSION_PATH__,
script = document.createElement('script');
let FLAVOR =
HOST.includes('player') ? 'player' :
HOST.includes('clips') ? 'clips' :
(location.pathname === '/p/ffz_bridge/' ? 'bridge' : 'avalon');
if (FLAVOR === 'clips' && location.pathname === '/embed')
FLAVOR = 'player';
script.id = 'ffz-script';
script.async = true;
script.crossOrigin = 'anonymous';
script.src = `${SERVER}/${FLAVOR}.js?_=${Date.now()}`;
document.head.appendChild(script);
})();

View file

@ -67,9 +67,11 @@ export default class LoadTracker extends Module {
data.success = true; data.success = true;
if ( ! data.pending.size ) { if ( ! data.pending.size ) {
this.log.debug('complete', type, Object.keys(data.timers)); const keys = Object.keys(data.timers);
this.log.debug('complete', type, keys);
if ( data.success ) if ( data.success )
this.emit(`:complete:${type}`); this.emit(`:complete:${type}`, keys);
this.pending_loads.delete(type); this.pending_loads.delete(type);
} }
} }

View file

@ -1271,7 +1271,7 @@ export default class Chat extends Module {
this.on('chat:get-tab-commands', event => { this.on('chat:get-tab-commands', event => {
event.commands.push({ event.commands.push({
name: 'ffz reload', name: 'ffz:reload',
description: this.i18n.t('chat.command.reload', 'Reload FFZ and add-on chat data (emotes, badges, etc.)'), description: this.i18n.t('chat.command.reload', 'Reload FFZ and add-on chat data (emotes, badges, etc.)'),
permissionLevel: 0, permissionLevel: 0,
ffz_group: 'FrankerFaceZ' ffz_group: 'FrankerFaceZ'
@ -1292,11 +1292,11 @@ export default class Chat extends Module {
this.emit('chat:reload-data'); this.emit('chat:reload-data');
}); });
this.on('load_tracker:complete:chat-data', () => { this.on('load_tracker:complete:chat-data', (list) => {
if ( this.triggered_reload ) { if ( this.triggered_reload ) {
const sc = this.resolve('site.chat'); const sc = this.resolve('site.chat');
if ( sc?.addNotice ) if ( sc?.addNotice )
sc.addNotice('*', this.i18n.t('chat.command.reload.done', 'FFZ has finished reloading data.')); sc.addNotice('*', this.i18n.t('chat.command.reload.done', 'FFZ has finished reloading data. (Sources: {list})', {list: list.join(', ')}));
} }
this.triggered_reload = false; this.triggered_reload = false;

View file

@ -22,7 +22,7 @@ const SHRINK_X = MODIFIER_FLAGS.ShrinkX,
const EMOTE_CLASS = 'chat-image chat-line__message--emote', const EMOTE_CLASS = 'chat-image chat-line__message--emote',
//WHITESPACE = /^\s*$/, //WHITESPACE = /^\s*$/,
//LINK_REGEX = /([^\w@#%\-+=:~])?((?:(https?:\/\/)?(?:[\w@#%\-+=:~]+\.)+[a-z]{2,6}(?:\/[\w./@#%&()\-+=:?~]*)?))([^\w./@#%&()\-+=:?~]|\s|$)/g, //LINK_REGEX = /([^\w@#%\-+=:~])?((?:(https?:\/\/)?(?:[\w@#%\-+=:~]+\.)+[a-z]{2,6}(?:\/[\w./@#%&()\-+=:?~]*)?))([^\w./@#%&()\-+=:?~]|\s|$)/g,
NEW_LINK_REGEX = /(?:(https?:\/\/)?((?:[\w#%\-+=:~]+\.)+[a-z]{2,10}(?:\/[\w./#%&@()\-+=:?~]*[^\.!,?])?))/g, NEW_LINK_REGEX = /(?:(https?:\/\/)?((?:[\w#%\-+=:~]+\.)+[a-z]{2,10}(?:\/[\w./#%&@()\-+=:?~]*[^\s\.!,?])?))/g,
//OLD_NEW_LINK_REGEX = /(?:(https?:\/\/)?((?:[\w#%\-+=:~]+\.)+[a-z]{2,10}(?:\/[\w./#%&@()\-+=:?~]*)?))/g, //OLD_NEW_LINK_REGEX = /(?:(https?:\/\/)?((?:[\w#%\-+=:~]+\.)+[a-z]{2,10}(?:\/[\w./#%&@()\-+=:?~]*)?))/g,
//MENTION_REGEX = /([^\w@#%\-+=:~])?(@([^\u0000-\u007F]+|\w+)+)([^\w./@#%&()\-+=:?~]|\s|$)/g; // eslint-disable-line no-control-regex //MENTION_REGEX = /([^\w@#%\-+=:~])?(@([^\u0000-\u007F]+|\w+)+)([^\w./@#%&()\-+=:?~]|\s|$)/g; // eslint-disable-line no-control-regex
MENTION_REGEX = /^(['"*([{<\\/]*)(@)((?:[^\u0000-\u007F]|[\w-])+)(?:\b|$)/; // eslint-disable-line no-control-regex MENTION_REGEX = /^(['"*([{<\\/]*)(@)((?:[^\u0000-\u007F]|[\w-])+)(?:\b|$)/; // eslint-disable-line no-control-regex

View file

@ -361,6 +361,12 @@ export default class Metadata extends Module {
} }
} }
// Get the video element.
const video = maybe_call(player.getHTMLVideoElement, player);
stats.avOffset = 0;
if ( video?._ffz_context )
stats.avOffset = (video._ffz_context_offset ?? 0) + video._ffz_context.currentTime - video.currentTime;
let tampered = false; let tampered = false;
try { try {
const url = player.core.state.path; const url = player.core.state.path;
@ -493,6 +499,14 @@ export default class Metadata extends Module {
stats stats
); );
const desync = data.avOffset !== 0
? (<div>{this.i18n.t(
'metadata.player-stats.av-offset',
'A/V Offset: {avOffset, number} seconds',
stats
)}</div>)
: null;
if ( data.old ) if ( data.old )
return [ return [
delayed, delayed,
@ -510,6 +524,7 @@ export default class Metadata extends Module {
<div class="tw-pd-t-05"> <div class="tw-pd-t-05">
{video_info} {video_info}
</div>, </div>,
desync,
tampered tampered
]; ];
@ -522,6 +537,7 @@ export default class Metadata extends Module {
<div class="tw-pd-t-05"> <div class="tw-pd-t-05">
{video_info} {video_info}
</div>, </div>,
desync,
tampered tampered
]; ];
} }

View file

@ -2227,7 +2227,7 @@ export default class ChatHook extends Module {
inst.sendMessage = function(msg, extra) { inst.sendMessage = function(msg, extra) {
msg = msg.replace(/\s+/g, ' '); msg = msg.replace(/\s+/g, ' ');
if ( msg.startsWith('/ffz') ) { if ( msg.startsWith('/ffz:') ) {
msg = msg.slice(5).trim(); msg = msg.slice(5).trim();
const idx = msg.indexOf(' '); const idx = msg.indexOf(' ');
let subcmd; let subcmd;
@ -2258,7 +2258,7 @@ export default class ChatHook extends Module {
else else
inst.addMessage({ inst.addMessage({
type: t.chat_types.Notice, type: t.chat_types.Notice,
message: t.i18n.t('chat.ffz-command.invalid', 'No such command: /ffz {subcmd}', {subcmd}) message: t.i18n.t('chat.ffz-command.invalid', 'No such command: /ffz:{subcmd}', {subcmd})
}); });
return false; return false;

View file

@ -402,13 +402,24 @@ export default class Input extends Module {
} }
checkForPreviews(inst, node) { checkForPreviews(inst, node) {
for(const el of node.querySelectorAll?.('span[data-a-target="chat-input-emote-preview"][aria-describedby]') ?? []) { // We can't find the tooltip element directly (without digging into React tree at least)
// So instead just find the relevant images in the document. This shouldn't happen TOO
// frequently, with any luck, so the performance impact should be small.
if ( node.querySelector?.('span[data-a-target="chat-input-emote-preview"]') ) {
for(const target of document.querySelectorAll('.tw-tooltip-layer img.chat-line__message--emote')) {
if ( target && target.src.startsWith('https://static-cdn.jtvnw.net/emoticons/v2/__FFZ__') )
this.updatePreview(inst, target);
}
}
// This no longer works because they removed aria-describedby
/*for(const el of node.querySelectorAll?.('span[data-a-target="chat-input-emote-preview"][aria-describedby]') ?? []) {
const cont = document.getElementById(el.getAttribute('aria-describedby')), const cont = document.getElementById(el.getAttribute('aria-describedby')),
target = cont && cont.querySelector('img.chat-line__message--emote'); target = cont && cont.querySelector('img.chat-line__message--emote');
if ( target && target.src.startsWith('https://static-cdn.jtvnw.net/emoticons/v2/__FFZ__') ) if ( target && target.src.startsWith('https://static-cdn.jtvnw.net/emoticons/v2/__FFZ__') )
this.updatePreview(inst, target); this.updatePreview(inst, target);
} }*/
for(const target of node.querySelectorAll?.('img.chat-line__message--emote')) { for(const target of node.querySelectorAll?.('img.chat-line__message--emote')) {
if ( target && (target.dataset.ffzId || target.src.startsWith('https://static-cdn.jtvnw.net/emoticons/v2/__FFZ__')) ) if ( target && (target.dataset.ffzId || target.src.startsWith('https://static-cdn.jtvnw.net/emoticons/v2/__FFZ__')) )

View file

@ -4,7 +4,11 @@ import {make_enum} from 'utilities/object';
export const DEBUG = localStorage.ffzDebugMode === 'true' && document.body.classList.contains('ffz-dev'); export const DEBUG = localStorage.ffzDebugMode === 'true' && document.body.classList.contains('ffz-dev');
export const EXTENSION = document.body.classList.contains('ffz-ext') && !!__extension__;
export const SERVER = DEBUG ? '//localhost:8000' : 'https://cdn.frankerfacez.com'; export const SERVER = DEBUG ? '//localhost:8000' : 'https://cdn.frankerfacez.com';
export const SERVER_OR_EXT = EXTENSION
? __extension__
: `${SERVER}/script`;
export const CLIENT_ID = 'a3bc9znoz6vi8ozsoca0inlcr4fcvkl'; export const CLIENT_ID = 'a3bc9znoz6vi8ozsoca0inlcr4fcvkl';
export const API_SERVER = '//api.frankerfacez.com'; export const API_SERVER = '//api.frankerfacez.com';

View file

@ -8,6 +8,7 @@ const VueLoaderPlugin = require('vue-loader/lib/plugin');
const VERSION = semver.parse(require('./package.json').version); const VERSION = semver.parse(require('./package.json').version);
const PRODUCTION = process.env.NODE_ENV === 'production'; const PRODUCTION = process.env.NODE_ENV === 'production';
const FOR_EXTENSION = !! process.env.FFZ_EXTENSION;
const ENTRY_POINTS = { const ENTRY_POINTS = {
bridge: './src/bridge.js', bridge: './src/bridge.js',
@ -36,7 +37,9 @@ module.exports = {
} }
], ],
output: { output: {
chunkFilename: '[name].[chunkhash].js', chunkFilename: FOR_EXTENSION
? '[name].js'
: '[name].[chunkhash].js',
path: path.resolve(__dirname, 'dist'), path: path.resolve(__dirname, 'dist'),
jsonpFunction: 'ffzWebpackJsonp', jsonpFunction: 'ffzWebpackJsonp',
crossOriginLoading: 'anonymous' crossOriginLoading: 'anonymous'
@ -58,7 +61,10 @@ module.exports = {
__version_major__: VERSION.major, __version_major__: VERSION.major,
__version_minor__: VERSION.minor, __version_minor__: VERSION.minor,
__version_patch__: VERSION.patch, __version_patch__: VERSION.patch,
__version_prerelease__: VERSION.prerelease __version_prerelease__: VERSION.prerelease,
__extension__: FOR_EXTENSION
? JSON.stringify(process.env.FFZ_EXTENSION)
: false
}), }),
], ],
module: { module: {
@ -67,7 +73,9 @@ module.exports = {
use: [{ use: [{
loader: 'file-loader', loader: 'file-loader',
options: { options: {
name: PRODUCTION ? '[name].[hash].css' : '[name].css' name: (! FOR_EXTENSION && PRODUCTION)
? '[name].[hash].css'
: '[name].css'
} }
}, { }, {
loader: 'extract-loader' loader: 'extract-loader'
@ -89,7 +97,9 @@ module.exports = {
type: 'javascript/auto', type: 'javascript/auto',
loader: 'file-loader', loader: 'file-loader',
options: { options: {
name: PRODUCTION ? '[name].[hash].json' : '[name].json' name: (! FOR_EXTENSION && PRODUCTION)
? '[name].[hash].json'
: '[name].json'
} }
}, },
{ {
@ -123,7 +133,9 @@ module.exports = {
use: [{ use: [{
loader: 'file-loader', loader: 'file-loader',
options: { options: {
name: PRODUCTION ? '[name].[hash].[ext]' : '[name].[ext]' name: (! FOR_EXTENSION && PRODUCTION)
? '[name].[hash].[ext]'
: '[name].[ext]'
} }
}] }]
}, },

View file

@ -12,13 +12,16 @@ const Terser = require('terser');
// Get Git info // Get Git info
const commit_hash = require('child_process').execSync('git rev-parse HEAD').toString().trim(); const commit_hash = require('child_process').execSync('git rev-parse HEAD').toString().trim();
const FOR_EXTENSION = !! process.env.FFZ_EXTENSION;
/* global module Buffer */ /* global module Buffer */
const minifier = content => { const minifier = content => {
const text = content.toString('utf8'); let text = content.toString('utf8');
if ( FOR_EXTENSION )
text = text.replace('__EXTENSION_PATH__', JSON.stringify(process.env.FFZ_EXTENSION));
const minified = Terser.minify(text); const minified = Terser.minify(text);
return (minified && minified.code) ? Buffer.from(minified.code) : content; return (minified && minified.code) ? Buffer.from(minified.code) : Buffer.from(text);
}; };
module.exports = merge(common, { module.exports = merge(common, {
@ -45,7 +48,9 @@ module.exports = merge(common, {
}), }),
new CopyPlugin([ new CopyPlugin([
{ {
from: './src/entry.js', from: FOR_EXTENSION
? './src/entry_ext.js'
: './src/entry.js',
to: 'script.min.js', to: 'script.min.js',
transform: minifier transform: minifier
} }
@ -62,7 +67,11 @@ module.exports = merge(common, {
], ],
output: { output: {
publicPath: '//cdn.frankerfacez.com/static/', publicPath: FOR_EXTENSION
filename: '[name].[hash].js' ? process.env.FFZ_EXTENSION
: '//cdn.frankerfacez.com/static/',
filename: FOR_EXTENSION
? '[name].js'
: '[name].[hash].js'
} }
}); });