mirror of
https://github.com/FrankerFaceZ/FrankerFaceZ.git
synced 2025-06-27 21:05:53 +00:00
4.51.0
This is the first release built with the updated build toolchain. Please let me know if you find anything unexpected that broke. * Added: Setting to treat known usernames in chat as mentions even without an at sign (@) prefix. * Added: Setting to automatically claim drops. * Added: Setting to hide the Subtember banner. * Changed: The stream latency metadata tool-tip now includes the buffer size value. * Fixed: The stream latency metadata was not appearing on the new mod view layout. * Fixed: The sub/bits leaderboard appearing in chat when it should be hidden by a setting. * Fixed: Incorrect text color on the Subtember banner.
This commit is contained in:
parent
10ca28098b
commit
588d3e3da9
13 changed files with 243 additions and 11 deletions
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "frankerfacez",
|
||||
"author": "Dan Salvato LLC",
|
||||
"version": "4.50.0",
|
||||
"version": "4.51.0",
|
||||
"description": "FrankerFaceZ is a Twitch enhancement suite.",
|
||||
"private": true,
|
||||
"license": "Apache-2.0",
|
||||
|
|
|
@ -900,6 +900,16 @@ export default class Chat extends Module {
|
|||
}
|
||||
});
|
||||
|
||||
this.settings.add('chat.filtering.all-mentions', {
|
||||
default: false,
|
||||
ui: {
|
||||
component: 'setting-check-box',
|
||||
path: 'Chat > Filtering > General >> Appearance',
|
||||
title: 'Display mentions for all users without requiring an at sign (@).',
|
||||
description: '**Note**: This setting can increase memory usage and impact chat performance.'
|
||||
}
|
||||
});
|
||||
|
||||
this.settings.add('chat.filtering.color-mentions', {
|
||||
default: false,
|
||||
ui: {
|
||||
|
@ -910,6 +920,13 @@ export default class Chat extends Module {
|
|||
}
|
||||
});
|
||||
|
||||
this.settings.add('chat.filtering.need-colors', {
|
||||
requires: ['chat.filtering.all-mentions' ,'chat.filtering.color-mentions'],
|
||||
process(ctx) {
|
||||
return ctx.get('chat.filtering.all-mentions') || ctx.get('chat.filtering.color-mentions')
|
||||
}
|
||||
});
|
||||
|
||||
this.settings.add('chat.filtering.bold-mentions', {
|
||||
default: true,
|
||||
ui: {
|
||||
|
@ -1218,7 +1235,7 @@ export default class Chat extends Module {
|
|||
room.buildBitsCSS();
|
||||
});
|
||||
|
||||
this.context.on('changed:chat.filtering.color-mentions', async val => {
|
||||
this.context.on('changed:chat.filtering.need-colors', async val => {
|
||||
if ( val )
|
||||
await this.createColorCache();
|
||||
else
|
||||
|
@ -1226,6 +1243,9 @@ export default class Chat extends Module {
|
|||
|
||||
this.emit(':update-line-tokens');
|
||||
});
|
||||
|
||||
this.context.on('changed:chat.filtering.all-mentions', () => this.emit(':update-line-tokens'));
|
||||
this.context.on('changed:chat.filtering.color-mentions', () => this.emit(':update-line-tokens'));
|
||||
}
|
||||
|
||||
|
||||
|
@ -1249,7 +1269,7 @@ export default class Chat extends Module {
|
|||
|
||||
this.on('site.subpump:pubsub-message', this.onPubSub, this);
|
||||
|
||||
if ( this.context.get('chat.filtering.color-mentions') )
|
||||
if ( this.context.get('chat.filtering.need-colors') )
|
||||
this.createColorCache().then(() => this.emit(':update-line-tokens'));
|
||||
|
||||
for(const key in TOKENIZERS)
|
||||
|
|
|
@ -313,6 +313,81 @@ export const Replies = {
|
|||
// Mentions
|
||||
// ============================================================================
|
||||
|
||||
function mention_processAll(tokens, msg, user, color_mentions) {
|
||||
const can_highlight_user = user && user.login && user.login == msg.user.login && ! this.context.get('chat.filtering.process-own'),
|
||||
priority = this.context.get('chat.filtering.mention-priority');
|
||||
|
||||
let login, display, mentionable = false;
|
||||
if ( user && user.login && ! can_highlight_user ) {
|
||||
login = user.login.toLowerCase();
|
||||
display = user.displayName && user.displayName.toLowerCase();
|
||||
if ( display === login )
|
||||
display = null;
|
||||
|
||||
mentionable = true;
|
||||
}
|
||||
|
||||
const out = [];
|
||||
for(const token of tokens) {
|
||||
if ( token.type !== 'text' ) {
|
||||
out.push(token);
|
||||
continue;
|
||||
}
|
||||
|
||||
let text = [];
|
||||
|
||||
for(const segment of token.text.split(/ +/)) {
|
||||
const match = /^(@?)(\S+?)(?:\b|$)/.exec(segment);
|
||||
if ( match ) {
|
||||
let recipient = match[2],
|
||||
has_at = match[1] === '@',
|
||||
mentioned = false;
|
||||
|
||||
const rlower = recipient ? recipient.toLowerCase() : '',
|
||||
color = this.color_cache ? this.color_cache.get(rlower) : null;
|
||||
|
||||
if ( rlower === login || rlower === display )
|
||||
mentioned = true;
|
||||
|
||||
if ( ! has_at && ! color && ! mentioned ) {
|
||||
text.push(segment);
|
||||
|
||||
} else {
|
||||
// If we have pending text, join it together.
|
||||
if ( text.length ) {
|
||||
out.push({
|
||||
type: 'text',
|
||||
text: `${text.join(' ')} `
|
||||
});
|
||||
text = [];
|
||||
}
|
||||
|
||||
out.push({
|
||||
type: 'mention',
|
||||
text: match[0],
|
||||
me: mentioned,
|
||||
color: color_mentions ? color : null,
|
||||
recipient: rlower
|
||||
});
|
||||
|
||||
if ( mentioned )
|
||||
this.applyHighlight(msg, priority, null, 'mention', true);
|
||||
|
||||
// Push the remaining text from the token.
|
||||
text.push(segment.substr(match[0].length));
|
||||
}
|
||||
|
||||
} else
|
||||
text.push(segment);
|
||||
}
|
||||
|
||||
if ( text.length > 1 || (text.length === 1 && text[0] !== '') )
|
||||
out.push({type: 'text', text: text.join(' ')})
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
export const Mentions = {
|
||||
type: 'mention',
|
||||
priority: 0,
|
||||
|
@ -346,6 +421,12 @@ export const Mentions = {
|
|||
if ( ! tokens || ! tokens.length )
|
||||
return;
|
||||
|
||||
const all_mentions = this.context.get('chat.filtering.all-mentions'),
|
||||
color_mentions = this.context.get('chat.filtering.color-mentions');
|
||||
|
||||
if ( all_mentions )
|
||||
return mention_processAll.call(this, tokens, msg, user, color_mentions);
|
||||
|
||||
const can_highlight_user = user && user.login && user.login == msg.user.login && ! this.context.get('chat.filtering.process-own'),
|
||||
priority = this.context.get('chat.filtering.mention-priority');
|
||||
|
||||
|
@ -396,7 +477,7 @@ export const Mentions = {
|
|||
}
|
||||
|
||||
const rlower = recipient ? recipient.toLowerCase() : '',
|
||||
color = this.color_cache ? this.color_cache.get(rlower) : null;
|
||||
color = (color_mentions && this.color_cache) ? this.color_cache.get(rlower) : null;
|
||||
|
||||
out.push({
|
||||
type: 'mention',
|
||||
|
|
|
@ -650,10 +650,12 @@ export default {
|
|||
|
||||
replayItem(item) {
|
||||
if ( item.pubsub ) {
|
||||
const channel = this.chat.ChatService.first?.props?.channelID;
|
||||
const channel = this.chat.ChatService.first?.props?.channelID,
|
||||
user = this.chat.resolve('site').getUser();
|
||||
|
||||
if ( this.replay_fix ) {
|
||||
item.topic = item.topic.replace(/<channel>/gi, channel);
|
||||
item.topic = item.topic.replace(/<user>/gi, user.id);
|
||||
// TODO: Crawl, replacing ids.
|
||||
// TODO: Update timestamps for pinned chat?
|
||||
}
|
||||
|
|
|
@ -38,5 +38,10 @@
|
|||
"name": "Hype Chat (PubSub)",
|
||||
"topic": "pinned-chat-updates-v1.<channel>",
|
||||
"data": {"type":"pin-message","data":{"id":"deea93ac-66c7-4500-aa38-d9cfb82f14bc","pinned_by":{"id":"128900149","display_name":"JailBreakRules"},"message":{"id":"deea93ac-66c7-4500-aa38-d9cfb82f14bc","sender":{"id":"128900149","display_name":"JailBreakRules","badges":[{"id":"subscriber","version":"12"},{"id":"glhf-pledge","version":"1"}],"chat_color":"#F31995"},"content":{"text":"xQc gets 70 cents off this lmfao","fragments":[{"text":"xQc gets 70 cents off this lmfao"}]},"type":"PAID","starts_at":1687737336,"updated_at":1687737336,"ends_at":1687737366,"sent_at":1687737336,"metadata":{"amount":"100","canonical-amount":"100","currency":"USD","exponent":"2","isSystemMessage":"false","level":"ONE"}}}}
|
||||
},
|
||||
{
|
||||
"name": "Drop Claim Reward (PubSub)",
|
||||
"topic": "user-drop-events.<user>",
|
||||
"data": {"type":"drop-claim","data":{"drop_instance_id":"9f21b210-63b0-4725-be46-b8e49207f533","drop_id":"4da46d69-269e-4709-baf0-1dc62dcf39b2","channel_id":"118336478"}}
|
||||
}
|
||||
]
|
|
@ -351,6 +351,7 @@ export default class Metadata extends Module {
|
|||
videoWidth,
|
||||
displayHeight,
|
||||
displayWidth,
|
||||
buffered: maybe_call(player.getBufferDuration, player) || -1,
|
||||
rate: maybe_call(player.getPlaybackRate, player),
|
||||
fps: Math.floor(maybe_call(player.getVideoFrameRate, player) || 0),
|
||||
hlsLatencyBroadcaster: maybe_call(player.getLiveLatency, player) || 0,
|
||||
|
@ -499,12 +500,22 @@ export default class Metadata extends Module {
|
|||
stats
|
||||
);
|
||||
|
||||
const desync = data.avOffset !== 0
|
||||
const desync = /*data.avOffset !== 0
|
||||
? (<div>{this.i18n.t(
|
||||
'metadata.player-stats.av-offset',
|
||||
'A/V Offset: {avOffset, number} seconds',
|
||||
stats
|
||||
)}</div>)
|
||||
:*/ null;
|
||||
|
||||
const buffer = stats.buffered > 0
|
||||
? (<div>{this.i18n.t(
|
||||
'metadata.player-stats.buffered',
|
||||
'Buffered: {buffered} seconds',
|
||||
{
|
||||
buffered: stats.buffered.toFixed(2)
|
||||
}
|
||||
)}</div>)
|
||||
: null;
|
||||
|
||||
if ( data.old )
|
||||
|
@ -525,6 +536,7 @@ export default class Metadata extends Module {
|
|||
{video_info}
|
||||
</div>,
|
||||
desync,
|
||||
buffer,
|
||||
tampered
|
||||
];
|
||||
|
||||
|
@ -538,6 +550,7 @@ export default class Metadata extends Module {
|
|||
{video_info}
|
||||
</div>,
|
||||
desync,
|
||||
buffer,
|
||||
tampered
|
||||
];
|
||||
}
|
||||
|
|
|
@ -190,6 +190,12 @@ export default class ChatHook extends Module {
|
|||
this.inject(Input);
|
||||
this.inject(ViewerCards);
|
||||
|
||||
this.ChatLeaderboard = this.fine.define(
|
||||
'chat-leaderboard',
|
||||
n => n.props?.topItems && has(n.props, 'forceMiniView') && has(n.props, 'leaderboardType'),
|
||||
Twilight.CHAT_ROUTES
|
||||
);
|
||||
|
||||
this.ChatService = this.fine.define(
|
||||
'chat-service',
|
||||
n => n.join && n.client && n.props.setChatConnectionAPI,
|
||||
|
@ -399,7 +405,7 @@ export default class ChatHook extends Module {
|
|||
this.settings.add('chat.banners.drops', {
|
||||
default: true,
|
||||
ui: {
|
||||
path: 'Chat > Appearance >> Community',
|
||||
path: 'Chat > Drops >> Appearance',
|
||||
title: 'Allow messages about Drops to be displayed in chat.',
|
||||
component: 'setting-check-box'
|
||||
}
|
||||
|
@ -490,6 +496,15 @@ export default class ChatHook extends Module {
|
|||
}
|
||||
});
|
||||
|
||||
this.settings.add('chat.drops.auto-rewards', {
|
||||
default: false,
|
||||
ui: {
|
||||
path: 'Chat > Drops >> Behavior',
|
||||
title: 'Automatically claim drops.',
|
||||
component: 'setting-check-box',
|
||||
}
|
||||
});
|
||||
|
||||
this.settings.add('chat.pin-resubs', {
|
||||
default: false,
|
||||
ui: {
|
||||
|
@ -1022,8 +1037,8 @@ export default class ChatHook extends Module {
|
|||
this.chat.context.getChanges('chat.bits.show', val =>
|
||||
this.css_tweaks.toggle('hide-bits', !val));
|
||||
|
||||
this.chat.context.getChanges('chat.bits.show-pinned', val =>
|
||||
this.css_tweaks.toggleHide('pinned-cheer', !val));
|
||||
this.chat.context.on('changed:chat.bits.show-pinned', () =>
|
||||
this.ChatLeaderboard.forceUpdate());
|
||||
|
||||
this.chat.context.getChanges('chat.filtering.deleted-style', val => {
|
||||
this.css_tweaks.toggle('chat-deleted-strike', val === 1 || val === 2);
|
||||
|
@ -1108,6 +1123,18 @@ export default class ChatHook extends Module {
|
|||
this.PointsInfo.on('unmount', () => this.updatePointsInfo(null));
|
||||
this.PointsInfo.ready(() => this.updatePointsInfo(this.PointsInfo.first));
|
||||
|
||||
this.ChatLeaderboard.ready(cls => {
|
||||
const old_render = cls.prototype.render;
|
||||
cls.prototype.render = function() {
|
||||
if ( ! t.chat.context.get('chat.bits.show-pinned') )
|
||||
return null;
|
||||
|
||||
return old_render.call(this);
|
||||
}
|
||||
|
||||
this.ChatLeaderboard.forceUpdate();
|
||||
});
|
||||
|
||||
this.GiftBanner.ready(cls => {
|
||||
const old_render = cls.prototype.render;
|
||||
cls.prototype.render = function() {
|
||||
|
@ -1180,6 +1207,12 @@ export default class ChatHook extends Module {
|
|||
if ( (ctype === 'mega-recipient-rewards' || ctype === 'mega-benefactor-rewards') && ! t.chat.context.get('chat.bits.show-rewards') )
|
||||
return null;
|
||||
|
||||
if ( ctype === 'drop' && ! this._ffz_auto_drop && t.chat.context.get('chat.drops.auto-rewards') )
|
||||
this._ffz_auto_drop = setTimeout(() => {
|
||||
this._ffz_auto_drop = null;
|
||||
t.autoClickDrop(this);
|
||||
}, 250);
|
||||
|
||||
} catch(err) {
|
||||
t.log.capture(err);
|
||||
t.log.error(err);
|
||||
|
@ -1478,6 +1511,23 @@ export default class ChatHook extends Module {
|
|||
}
|
||||
|
||||
|
||||
autoClickDrop(inst) {
|
||||
const callout = inst.props?.callouts?.[0] || inst.props?.pinnedCallout,
|
||||
ctype = callout?.event?.type;
|
||||
|
||||
if ( ctype !== 'drop' || ! this.chat.context.get('chat.drops.auto-rewards') )
|
||||
return;
|
||||
|
||||
const node = this.fine.getHostNode(inst),
|
||||
btn = node.querySelector('button[data-a-target="chat-private-callout__primary-button"]');
|
||||
|
||||
if ( ! btn )
|
||||
return;
|
||||
|
||||
btn.click();
|
||||
}
|
||||
|
||||
|
||||
wrapRaidController(inst) {
|
||||
if ( inst._ffz_wrapped )
|
||||
return this.noAutoRaids(inst);
|
||||
|
|
|
@ -31,6 +31,8 @@ const CLASSES = {
|
|||
'prime-offers': '.top-nav__prime',
|
||||
'discover-luna': '.top-nav__external-link[data-a-target="try-presto-link"]',
|
||||
|
||||
'subtember': '.subtember-gradient',
|
||||
|
||||
'player-gain-volume': '.video-player__container[data-compressed="true"] .volume-slider__slider-container:not(.ffz--player-gain)',
|
||||
|
||||
'player-ext': '.video-player .extension-taskbar,.video-player .extension-container,.video-player .extensions-dock__layout,.video-player .extensions-notifications,.video-player .extensions-video-overlay-size-container,.video-player .extensions-dock__layout',
|
||||
|
@ -318,6 +320,15 @@ export default class CSSTweaks extends Module {
|
|||
}
|
||||
});
|
||||
|
||||
this.settings.add('layout.subtember', {
|
||||
default: true,
|
||||
ui: {
|
||||
path: 'Appearance > Layout >> Channel',
|
||||
title: 'Allow the Subtember upsell banner to appear.',
|
||||
component: 'setting-check-box'
|
||||
}
|
||||
});
|
||||
|
||||
this.settings.add('layout.discover', {
|
||||
default: true,
|
||||
ui: {
|
||||
|
@ -499,6 +510,8 @@ export default class CSSTweaks extends Module {
|
|||
this.toggleHide('whispers', !this.settings.get('whispers.show'));
|
||||
this.toggleHide('celebration', ! this.settings.get('channel.show-celebrations'));
|
||||
|
||||
this.settings.getChanges('layout.subtember', val => this.toggleHide('subtember', !val));
|
||||
|
||||
this.updateFont();
|
||||
this.updateTopNav();
|
||||
|
||||
|
|
|
@ -228,6 +228,11 @@ export default class ModView extends Module {
|
|||
//if ( channel?.id && channel.id != this._cached_id )
|
||||
// this.checkRoot();
|
||||
|
||||
if ( this._cached_bar_id != channel?.id ) {
|
||||
this._cached_bar_id = channel?.id;
|
||||
this._cached_bar_channel = channel;
|
||||
}
|
||||
|
||||
if ( title != el._cached_title || game?.id != el._cached_game ) {
|
||||
el._cached_title = title;
|
||||
el._cached_game = game?.id;
|
||||
|
@ -277,7 +282,7 @@ export default class ModView extends Module {
|
|||
|
||||
updateMetadata(el, keys) {
|
||||
const cont = el._ffz_cont,
|
||||
channel = this._cached_channel;
|
||||
channel = this._cached_bar_channel;
|
||||
//root = this.fine.getReactInstance(el);
|
||||
|
||||
/*let channel = null, state = root?.return?.memoizedState, i = 0;
|
||||
|
|
|
@ -130,3 +130,10 @@ html, .tw-root--theme-dark, .tw-root--theme-light {
|
|||
border-color: var(--color-background-button-primary-default) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.subtember-gradient {
|
||||
--color-text-base: #0e0c10;
|
||||
--color-fill-button-icon: #0e0c10;
|
||||
|
||||
color: var(--color-text-base);
|
||||
}
|
||||
|
|
|
@ -36,6 +36,31 @@ export default class SocketClient extends Module {
|
|||
getSocket: () => this,
|
||||
});
|
||||
|
||||
this.settings.add('auth.mode', {
|
||||
default: 'chat',
|
||||
|
||||
ui: {
|
||||
path: 'Data Management > Authentication >> General',
|
||||
title: 'Authentication Provider',
|
||||
description: 'Which method should the FrankerFaceZ client use to authenticate against the FFZ servers when necessary?',
|
||||
component: 'setting-select-box',
|
||||
force_seen: true,
|
||||
|
||||
data: [
|
||||
{
|
||||
value: 'chat',
|
||||
title: 'Twitch Chat'
|
||||
},
|
||||
{
|
||||
value: false,
|
||||
title: 'Disabled (No Authentication)'
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
changed: () => this._cached_token = null
|
||||
});
|
||||
|
||||
this.settings.add('socket.use-cluster', {
|
||||
default: 'Production',
|
||||
|
||||
|
@ -135,6 +160,15 @@ export default class SocketClient extends Module {
|
|||
// ========================================================================
|
||||
|
||||
getAPIToken() {
|
||||
const mode = this.settings.get('auth.mode');
|
||||
|
||||
if ( mode === 'chat' )
|
||||
return this._getAPIToken_Chat();
|
||||
|
||||
return Promise.reject(new Error('The user has disabled authentication.'));
|
||||
}
|
||||
|
||||
_getAPIToken_Chat() {
|
||||
if ( this._cached_token ) {
|
||||
if ( this._cached_token.expires > (Date.now() + 15000) )
|
||||
return Promise.resolve(this._cached_token);
|
||||
|
|
|
@ -619,6 +619,8 @@ export class FineWrapper extends EventEmitter {
|
|||
this._wrapped.add('UNSAFE_componentWillMount');
|
||||
this._wrapped.add('componentWillUnmount');
|
||||
|
||||
cls._ffz_wrapper = this;
|
||||
|
||||
const t = this,
|
||||
_instances = this.instances,
|
||||
proto = cls.prototype,
|
||||
|
|
|
@ -90,7 +90,7 @@ const config = {
|
|||
output: {
|
||||
chunkFormat: 'array-push',
|
||||
clean: true,
|
||||
publicPath: true
|
||||
publicPath: FOR_EXTENSION
|
||||
? 'auto'
|
||||
: FILE_PATH,
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue