1
0
Fork 0
mirror of https://github.com/FrankerFaceZ/FrankerFaceZ.git synced 2025-06-27 21:05:53 +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_patch__': false,
'__version_prerelease__': false,
'__extension__': false,
'FrankerFaceZ': false
},
'rules': {

View file

@ -1,7 +1,7 @@
{
"name": "frankerfacez",
"author": "Dan Salvato LLC",
"version": "4.47.1",
"version": "4.48.0",
"description": "FrankerFaceZ is a Twitch enhancement suite.",
"private": true,
"license": "Apache-2.0",

View file

@ -5,7 +5,7 @@
// ============================================================================
import Module from 'utilities/module';
import { SERVER } from 'utilities/constants';
import { EXTENSION, SERVER_OR_EXT } from 'utilities/constants';
import { createElement } from 'utilities/dom';
import { timeout, has } from 'utilities/object';
import { getBuster } from 'utilities/time';
@ -69,15 +69,16 @@ export default class AddonManager extends Module {
off: (...args) => this.off(...args)
});
this.settings.add('addons.dev.server', {
default: false,
ui: {
path: 'Add-Ons >> Development',
title: 'Use Local Development Server',
description: 'Attempt to load add-ons from local development server on port 8001.',
component: 'setting-check-box'
}
});
if ( ! EXTENSION )
this.settings.add('addons.dev.server', {
default: false,
ui: {
path: 'Add-Ons >> Development',
title: 'Use Local Development Server',
description: 'Attempt to load add-ons from local development server on port 8001.',
component: 'setting-check-box'
}
});
this.on('i18n:update', this.rebuildAddonSearch, this);
@ -151,9 +152,13 @@ export default class AddonManager extends Module {
async loadAddonData() {
const [cdn_data, local_data] = await Promise.all([
fetchJSON(`${SERVER}/script/addons.json?_=${getBuster(30)}`),
this.settings.get('addons.dev.server') ?
fetchJSON(`https://localhost:8001/script/addons.json?_=${getBuster()}`) : null
fetchJSON(`${SERVER_OR_EXT}/addons.json?_=${getBuster(30)}`),
// 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) )
@ -469,7 +474,7 @@ export default class AddonManager extends Module {
document.head.appendChild(createElement('script', {
id: `ffz-loaded-addon-${addon.id}`,
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'
}));

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;
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 )
this.emit(`:complete:${type}`);
this.emit(`:complete:${type}`, keys);
this.pending_loads.delete(type);
}
}

View file

@ -1271,7 +1271,7 @@ export default class Chat extends Module {
this.on('chat:get-tab-commands', event => {
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.)'),
permissionLevel: 0,
ffz_group: 'FrankerFaceZ'
@ -1292,11 +1292,11 @@ export default class Chat extends Module {
this.emit('chat:reload-data');
});
this.on('load_tracker:complete:chat-data', () => {
this.on('load_tracker:complete:chat-data', (list) => {
if ( this.triggered_reload ) {
const sc = this.resolve('site.chat');
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;

View file

@ -22,7 +22,7 @@ const SHRINK_X = MODIFIER_FLAGS.ShrinkX,
const EMOTE_CLASS = 'chat-image chat-line__message--emote',
//WHITESPACE = /^\s*$/,
//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,
//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

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;
try {
const url = player.core.state.path;
@ -493,6 +499,14 @@ export default class Metadata extends Module {
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 )
return [
delayed,
@ -510,6 +524,7 @@ export default class Metadata extends Module {
<div class="tw-pd-t-05">
{video_info}
</div>,
desync,
tampered
];
@ -522,6 +537,7 @@ export default class Metadata extends Module {
<div class="tw-pd-t-05">
{video_info}
</div>,
desync,
tampered
];
}

View file

@ -2227,7 +2227,7 @@ export default class ChatHook extends Module {
inst.sendMessage = function(msg, extra) {
msg = msg.replace(/\s+/g, ' ');
if ( msg.startsWith('/ffz') ) {
if ( msg.startsWith('/ffz:') ) {
msg = msg.slice(5).trim();
const idx = msg.indexOf(' ');
let subcmd;
@ -2258,7 +2258,7 @@ export default class ChatHook extends Module {
else
inst.addMessage({
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;

View file

@ -402,13 +402,24 @@ export default class Input extends Module {
}
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')),
target = cont && cont.querySelector('img.chat-line__message--emote');
if ( target && target.src.startsWith('https://static-cdn.jtvnw.net/emoticons/v2/__FFZ__') )
this.updatePreview(inst, target);
}
}*/
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__')) )

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 EXTENSION = document.body.classList.contains('ffz-ext') && !!__extension__;
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 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 PRODUCTION = process.env.NODE_ENV === 'production';
const FOR_EXTENSION = !! process.env.FFZ_EXTENSION;
const ENTRY_POINTS = {
bridge: './src/bridge.js',
@ -36,7 +37,9 @@ module.exports = {
}
],
output: {
chunkFilename: '[name].[chunkhash].js',
chunkFilename: FOR_EXTENSION
? '[name].js'
: '[name].[chunkhash].js',
path: path.resolve(__dirname, 'dist'),
jsonpFunction: 'ffzWebpackJsonp',
crossOriginLoading: 'anonymous'
@ -58,7 +61,10 @@ module.exports = {
__version_major__: VERSION.major,
__version_minor__: VERSION.minor,
__version_patch__: VERSION.patch,
__version_prerelease__: VERSION.prerelease
__version_prerelease__: VERSION.prerelease,
__extension__: FOR_EXTENSION
? JSON.stringify(process.env.FFZ_EXTENSION)
: false
}),
],
module: {
@ -67,7 +73,9 @@ module.exports = {
use: [{
loader: 'file-loader',
options: {
name: PRODUCTION ? '[name].[hash].css' : '[name].css'
name: (! FOR_EXTENSION && PRODUCTION)
? '[name].[hash].css'
: '[name].css'
}
}, {
loader: 'extract-loader'
@ -89,7 +97,9 @@ module.exports = {
type: 'javascript/auto',
loader: 'file-loader',
options: {
name: PRODUCTION ? '[name].[hash].json' : '[name].json'
name: (! FOR_EXTENSION && PRODUCTION)
? '[name].[hash].json'
: '[name].json'
}
},
{
@ -123,7 +133,9 @@ module.exports = {
use: [{
loader: 'file-loader',
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
const commit_hash = require('child_process').execSync('git rev-parse HEAD').toString().trim();
const FOR_EXTENSION = !! process.env.FFZ_EXTENSION;
/* global module Buffer */
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);
return (minified && minified.code) ? Buffer.from(minified.code) : content;
return (minified && minified.code) ? Buffer.from(minified.code) : Buffer.from(text);
};
module.exports = merge(common, {
@ -45,7 +48,9 @@ module.exports = merge(common, {
}),
new CopyPlugin([
{
from: './src/entry.js',
from: FOR_EXTENSION
? './src/entry_ext.js'
: './src/entry.js',
to: 'script.min.js',
transform: minifier
}
@ -62,7 +67,11 @@ module.exports = merge(common, {
],
output: {
publicPath: '//cdn.frankerfacez.com/static/',
filename: '[name].[hash].js'
publicPath: FOR_EXTENSION
? process.env.FFZ_EXTENSION
: '//cdn.frankerfacez.com/static/',
filename: FOR_EXTENSION
? '[name].js'
: '[name].[hash].js'
}
});