1
0
Fork 0
mirror of https://github.com/FrankerFaceZ/FrankerFaceZ.git synced 2025-06-27 21:05:53 +00:00
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:
SirStendec 2023-09-06 16:10:47 -04:00
parent 10ca28098b
commit 588d3e3da9
13 changed files with 243 additions and 11 deletions

View file

@ -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",

View file

@ -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)

View file

@ -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',

View file

@ -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?
}

View file

@ -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"}}
}
]

View file

@ -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
];
}

View file

@ -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);

View file

@ -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();

View file

@ -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;

View file

@ -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);
}

View file

@ -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);

View file

@ -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,

View file

@ -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'),