From f2e7694b3b79fc5aa1eb11d91007d8c705bf4fc7 Mon Sep 17 00:00:00 2001 From: SirStendec Date: Tue, 3 Dec 2024 21:09:28 -0500 Subject: [PATCH] 4.76.1 * Added: Setting to hide the Cast button on the player. * Changed: Add support for a directory experiment. * Fixed: Improve the reliability of channel page tweaks in certain situations, such as co-streaming. * Fixed: The new feature to hide the native Clip button not working due to a version control mistake. --- package.json | 2 +- .../css_tweaks/player-hide-native-clip.scss | 3 + src/sites/player/index.jsx | 3 +- src/sites/shared/player.jsx | 12 ++ src/sites/twitch-twilight/modules/channel.jsx | 39 ++-- .../modules/css_tweaks/index.js | 3 +- .../styles/player-hide-native-clip.scss | 3 + .../modules/directory/index.jsx | 178 +++++++++++------- 8 files changed, 152 insertions(+), 91 deletions(-) create mode 100644 src/sites/player/css_tweaks/player-hide-native-clip.scss create mode 100644 src/sites/twitch-twilight/modules/css_tweaks/styles/player-hide-native-clip.scss diff --git a/package.json b/package.json index 13767bb2..aa75bbd3 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "frankerfacez", "author": "Dan Salvato LLC", - "version": "4.76.0", + "version": "4.76.1", "description": "FrankerFaceZ is a Twitch enhancement suite.", "private": true, "license": "Apache-2.0", diff --git a/src/sites/player/css_tweaks/player-hide-native-clip.scss b/src/sites/player/css_tweaks/player-hide-native-clip.scss new file mode 100644 index 00000000..d151d0df --- /dev/null +++ b/src/sites/player/css_tweaks/player-hide-native-clip.scss @@ -0,0 +1,3 @@ +.player-controls__right-control-group button[aria-label*="alt+x"] { + display: none !important; +} diff --git a/src/sites/player/index.jsx b/src/sites/player/index.jsx index 0fe4124b..1a8a654c 100644 --- a/src/sites/player/index.jsx +++ b/src/sites/player/index.jsx @@ -35,6 +35,7 @@ export default class PlayerSite extends BaseSite { this.css_tweaks.rules = { 'unfollow-button': '.follow-btn--following', + 'player-cast': '.video-player button:has(.tw-chromecast-button__icon)', 'player-gain-volume': '.video-player__overlay[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', 'player-ext-hover': '.video-player__overlay[data-controls="false"] .extension-taskbar,.video-player__overlay[data-controls="false"] .extension-container,.video-player__overlay[data-controls="false"] .extensions-dock__layout,.video-player__overlay[data-controls="false"] .extensions-notifications,.video-player__overlay[data-controls="false"] .extensions-video-overlay-size-container' @@ -209,4 +210,4 @@ export default class PlayerSite extends BaseSite { lbl.textContent = btn.title = this.i18n.t('site.menu_button', 'FrankerFaceZ Control Center'); ver.textContent = this.resolve('core').constructor.version_info.toString(); } -} \ No newline at end of file +} diff --git a/src/sites/shared/player.jsx b/src/sites/shared/player.jsx index 06b3288f..a3add287 100644 --- a/src/sites/shared/player.jsx +++ b/src/sites/shared/player.jsx @@ -93,6 +93,17 @@ export default class PlayerBase extends Module { } }); + this.settings.add('player.cast-button.hide', { + default: false, + ui: { + path: 'Player > General >> Appearance', + component: 'setting-check-box', + title: 'Hide the Cast button.', + }, + + changed: val => this.css_tweaks.toggleHide('player-cast', val) + }); + this.settings.add('player.clip-button.hide-native', { default: null, requires: ['player.clip-button.custom'], @@ -627,6 +638,7 @@ export default class PlayerBase extends Module { await this.settings.awaitProvider(); await this.settings.provider.awaitReady(); + this.css_tweaks.toggleHide('player-cast', this.settings.get('player.cast-button.hide')); this.css_tweaks.toggleHide('player-gain-volume', this.settings.get('player.gain.no-volume')); this.css_tweaks.toggle('player-hide-native-clip', this.settings.get('player.clip-button.hide-native')); this.css_tweaks.toggle('player-volume', this.settings.get('player.volume-always-shown')); diff --git a/src/sites/twitch-twilight/modules/channel.jsx b/src/sites/twitch-twilight/modules/channel.jsx index 0e50cbbc..532f33a7 100644 --- a/src/sites/twitch-twilight/modules/channel.jsx +++ b/src/sites/twitch-twilight/modules/channel.jsx @@ -119,7 +119,7 @@ export default class Channel extends Module { ); this.InfoBar = this.elemental.define( - 'channel-info-bar', '.channel-info-content', + 'channel-info-bar', '#live-channel-stream-information', //.channel-info-content', USER_PAGES, {childNodes: true, subtree: true}, 1 ); @@ -305,9 +305,29 @@ export default class Channel extends Module { el._ffz_link_login = null; } + let nvc = el.querySelector('.ffz--native-viewers-container'); + if ( ! nvc ) { + 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'); + nvc = vel; + break; + } + vel = vel.parentElement; + i++; + } + } + if ( ! el._ffz_cont ) { - const report = el.querySelector('.report-button,button[data-test-selector="video-options-button"],button[data-test-selector="clip-options-button"],button[data-a-target="report-button-more-button"]'); - let cont = report && (report.closest('.tw-flex-wrap.tw-justify-content-end') || report.closest('.tw-justify-content-end')); + let report = el.querySelector(`.report-button,button[data-test-selector="video-options-button"],button[data-test-selector="clip-options-button"],button[data-a-target="report-button-more-button"]`); + if (!report && nvc) + report = nvc.parentElement; + let cont = report && ( + report.closest('.tw-flex-wrap.tw-justify-content-end') || + report.closest('.tw-justify-content-end') + ); if ( ! cont && report ) { cont = report.parentElement?.parentElement; @@ -334,19 +354,6 @@ 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); let props = react?.child?.memoizedProps; if ( ! props?.channelLogin ) diff --git a/src/sites/twitch-twilight/modules/css_tweaks/index.js b/src/sites/twitch-twilight/modules/css_tweaks/index.js index 34048f25..50be58c4 100644 --- a/src/sites/twitch-twilight/modules/css_tweaks/index.js +++ b/src/sites/twitch-twilight/modules/css_tweaks/index.js @@ -37,6 +37,7 @@ const CLASSES = { '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', 'player-ext-hover': '.video-player__container[data-controls="false"] .extension-taskbar,.video-player__container[data-controls="false"] .extension-container,.video-player__container[data-controls="false"] .extensions-dock__layout,.video-player__container[data-controls="false"] .extensions-notifications,.video-player__container[data-controls="false"] .extensions-video-overlay-size-container', + 'player-cast': '.video-player button:has(.tw-chromecast-button__icon)', 'player-event-bar': '.channel-root .live-event-banner-ui__header', 'player-rerun-bar': '.channel-root__player-container div.tw-c-text-overlay:not([data-a-target="hosting-ui-header"])', @@ -44,7 +45,7 @@ const CLASSES = { 'pinned-cheer': '.pinned-cheer,.pinned-cheer-v2,.channel-leaderboard,.channel-leaderboard-marquee,div[data-test-selector="channel-leaderboard-container"]', 'whispers': 'body .whispers-open-threads,.tw-core-button[data-a-target="whisper-box-button"],.whispers__pill', - 'dir-live-ind': '.live-channel-card[data-ffz-type="live"] .tw-channel-status-text-indicator, article[data-ffz-type="live"] .tw-channel-status-text-indicator', + 'dir-live-ind': `.live-channel-card[data-ffz-type="live"] .tw-channel-status-text-indicator,article[data-ffz-type="live"] .tw-channel-status-text-indicator,.switcher-hopper__scroll-container .tw-tower a[data-ffz-type="live"] .tw-channel-status-text-indicator`, 'profile-hover': '.preview-card .tw-relative:hover .ffz-channel-avatar', 'not-live-bar': 'div[data-test-selector="non-live-video-banner-layout"]', 'channel-live-ind': '.channel-header__user .tw-channel-status-text-indicator,.channel-info-content .tw-halo__indicator', diff --git a/src/sites/twitch-twilight/modules/css_tweaks/styles/player-hide-native-clip.scss b/src/sites/twitch-twilight/modules/css_tweaks/styles/player-hide-native-clip.scss new file mode 100644 index 00000000..d151d0df --- /dev/null +++ b/src/sites/twitch-twilight/modules/css_tweaks/styles/player-hide-native-clip.scss @@ -0,0 +1,3 @@ +.player-controls__right-control-group button[aria-label*="alt+x"] { + display: none !important; +} diff --git a/src/sites/twitch-twilight/modules/directory/index.jsx b/src/sites/twitch-twilight/modules/directory/index.jsx index 48aee535..579520bb 100644 --- a/src/sites/twitch-twilight/modules/directory/index.jsx +++ b/src/sites/twitch-twilight/modules/directory/index.jsx @@ -62,7 +62,13 @@ export default class Directory extends Module { this.DirectoryCard = this.elemental.define( 'directory-card', - 'article[data-a-target^="video-carousel-card-"],article[data-a-target^="followed-vod-"],article[data-a-target^="card-"],div[data-a-target^="video-tower-card-"] article,div[data-a-target^="clips-card-"] article,.shelf-card__impression-wrapper article,.tw-tower div article', + `article[data-a-target^="video-carousel-card-"],article[data-a-target^="followed-vod-"],article[data-a-target^="card-"],div[data-a-target^="video-tower-card-"] article,div[data-a-target^="clips-card-"] article,.shelf-card__impression-wrapper article,.tw-tower div article`, + DIR_ROUTES, null, 0, 0 + ); + + this.DirectoryExperimentCard = this.elemental.define( + 'directory-experiment-card', + '.switcher-hopper__scroll-container .tw-tower > div > a', DIR_ROUTES, null, 0, 0 ); @@ -531,6 +537,11 @@ export default class Directory extends Module { this.on('i18n:update', () => this.updateCards()); + this.DirectoryExperimentCard.on('mount', this.updateExpCard, this); + this.DirectoryExperimentCard.on('mutate', this.updateExpCard, this); + this.DirectoryExperimentCard.on('unmount', this.clearExpCard, this); + this.DirectoryExperimentCard.each(el => this.updateExpCard(el)); + this.DirectoryCard.on('mount', this.updateCard, this); this.DirectoryCard.on('mutate', this.updateCard, this); this.DirectoryCard.on('unmount', this.clearCard, this); @@ -647,27 +658,53 @@ export default class Directory extends Module { hide_container.classList.toggle('tw-hide', should_hide); } + updateCards() { + this.DirectoryCard.each(el => this.updateCard(el)); + this.DirectoryExperimentCard.each(el => this.updateExpCard(el)); + this.DirectoryGameCard.each(el => this.updateGameCard(el)); + this.DirectoryShelf.forceUpdate(); + + this.emit(':update-cards'); + } + + clearCard(el, for_exp = false) { + this.clearUptime(el); + this.clearFlags(el); + + const cont = this._getTopRightContainer(el, for_exp); + if ( cont ) + cont.remove(); + + el._ffz_top_right = null; + } + + clearExpCard(el) { + return this.clearCard(el, true); + } updateCard(el) { - const react = this.fine.getReactInstance(el); - if ( ! react ) + const parent = this.fine.searchParentNode(el, n => n.memoizedProps?.channelLogin); + if ( ! parent ) return; - let props = react.child?.memoizedProps; - if ( ! props?.channelLogin ) - props = react.return?.stateNode?.props; + const props = parent.memoizedProps; + return this._updateCard(el, false, props, props.trackingProps); + } - if ( ! props?.channelLogin ) - props = react.return?.return?.return?.memoizedProps; - - if ( ! props?.channelLogin ) - props = react.return?.return?.return?.return?.return?.memoizedProps; - - if ( ! props?.channelLogin ) + updateExpCard(el) { + const parent = this.fine.searchParentNode(el, n => n.memoizedProps?.item?.channelID); + if ( ! parent ) return; - const game = props.gameTitle || props.trackingProps?.categoryName || props.trackingProps?.category || props.contextualCardActionProps?.props?.categoryName, - tags = props.tagListProps?.freeformTags; + const props = parent.memoizedProps, + item = props.item; + + return this._updateCard(el, true, item, props.tracking); + } + + _updateCard(el, for_exp, item, tracking) { + const game = item.categoryName || item.gameTitle || tracking?.game || tracking?.categoryName || tracking?.category, + tags = item.tags ?? item.tagListProps?.freeformTags; const need_flags = this.settings.get('directory.wait-flags'), show_flags = this.settings.get('directory.show-flags'), @@ -680,12 +717,12 @@ export default class Directory extends Module { el._ffz_flags = null; // Are we getting a clip, a video, or a stream? - if ( props.slug ) { + if ( item.slug || item.type === 'clips' ) { // Clip //console.log('need flags for clip', props.slug); el._ffz_flags = []; - } else if ( props.vodID ) { + } else if ( item.vodID || item.type === 'videos' ) { // Video //console.log('need flags for vod', props.vodID); el._ffz_flags = []; @@ -693,9 +730,11 @@ export default class Directory extends Module { } else { // Stream? //console.log('need flags for stream', props.channelLogin); - this.twitch_data.getStreamFlags(null, props.channelLogin).then(data => { + this.twitch_data.getStreamFlags(item.channelID, item.channelID ? null : item.channelLogin).then(data => { el._ffz_flags = data ?? []; - this.updateCard(el); + for_exp + ? this.updateExpCard(el) + : this.updateCard(el); }); } } @@ -742,22 +781,24 @@ export default class Directory extends Module { if ( regexes[1] ) regexes[1].lastIndex = -1; - if (( regexes[0] && regexes[0].test(props.title) ) || ( regexes[1] && regexes[1].test(props.title) )) + if (( regexes[0] && regexes[0].test(item.title) ) || ( regexes[1] && regexes[1].test(item.title) )) should_blur = true; } } + const type = item.type ?? item.streamType; + el.classList.toggle('ffz-hide-thumbnail', should_blur); - el.dataset.ffzType = props.streamType; + el.dataset.ffzType = type; let should_hide = false; if ( bad_tag ) should_hide = true; - else if ( props.streamType === 'rerun' && this.settings.get('directory.hide-vodcasts') ) + else if ( type === 'rerun' && this.settings.get('directory.hide-vodcasts') ) should_hide = true; - else if ( props.context != null && props.context !== CARD_CONTEXTS.SingleGameList && this.settings.provider.get('directory.game.blocked-games', []).includes(game) ) + else if ( item.context != null && item.context !== CARD_CONTEXTS.SingleGameList && this.settings.provider.get('directory.game.blocked-games', []).includes(game) ) should_hide = true; - else if ( (props.isPromotion || props.sourceType === 'COMMUNITY_BOOST' || props.sourceType === 'PROMOTION' || props.sourceType === 'SPONSORED') && this.settings.get('directory.hide-promoted') ) + else if ( (item.isPromotion || item.sourceType === 'COMMUNITY_BOOST' || item.sourceType === 'PROMOTION' || item.sourceType === 'SPONSORED') && this.settings.get('directory.hide-promoted') ) should_hide = true; else { if ( block_flags.size && el._ffz_flags ) { @@ -776,7 +817,7 @@ export default class Directory extends Module { if ( regexes[1] ) regexes[1].lastIndex = -1; - if (( regexes[0] && regexes[0].test(props.channelLogin) ) || ( regexes[1] && regexes[1].test(props.channelLogin) )) + if (( regexes[0] && regexes[0].test(item.channelLogin) ) || ( regexes[1] && regexes[1].test(item.channelLogin) )) should_hide = true; } } @@ -789,7 +830,7 @@ export default class Directory extends Module { if ( regexes[1] ) regexes[1].lastIndex = -1; - if (( regexes[0] && regexes[0].test(props.title) ) || ( regexes[1] && regexes[1].test(props.title) )) + if (( regexes[0] && regexes[0].test(item.title) ) || ( regexes[1] && regexes[1].test(item.title) )) should_hide = true; } } @@ -799,14 +840,14 @@ export default class Directory extends Module { if ( ! hide_container ) hide_container = el; - if ( hide_container.querySelectorAll('a[data-a-target="preview-card-image-link"]').length < 2 ) + if ( hide_container.querySelectorAll('.tw-aspect .tw-image').length < 2 ) hide_container.classList.toggle('tw-hide', should_hide); - this.updateUptime(el, props); - this.updateFlags(el); + this.updateUptime(el, item, for_exp); + this.updateFlags(el, for_exp); } - updateFlags(el) { + updateFlags(el, for_exp = false) { if ( ! document.contains(el) ) return this.clearFlags(el); @@ -815,7 +856,7 @@ export default class Directory extends Module { if ( ! setting || ! el._ffz_flags?.length ) return this.clearFlags(el); - const container = this._getTopRightContainer(el); + const container = this._getTopRightContainer(el, true, for_exp); if ( ! container ) return this.clearFlags(el); @@ -827,6 +868,9 @@ export default class Directory extends Module { {el.ffz_flags_tt =
}
)); + else if ( ! el.contains(el.ffz_flags_el) ) + container.appendChild(el.ffz_flags_el); + el.ffz_flags_tt.textContent = this.i18n.t('metadata.flags.tooltip', 'Intended for certain audiences. May contain:') + '\n\n' + el._ffz_flags.map(x => x.localizedName).join('\n'); @@ -836,34 +880,21 @@ export default class Directory extends Module { if ( el.ffz_flags_el ) { el.ffz_flags_el.remove(); el.ffz_flags_tt = null; + el.ffz_flags_el = null; } } - updateCards() { - this.DirectoryCard.each(el => this.updateCard(el)); - this.DirectoryGameCard.each(el => this.updateGameCard(el)); - this.DirectoryShelf.forceUpdate(); - this.emit(':update-cards'); - } - - clearCard(el) { - this.clearUptime(el); - this.clearFlags(el); - - const cont = this._getTopRightContainer(el, false); - if ( cont ) - cont.remove(); - - el._ffz_top_right = null; - } - - _getTopRightContainer(el, should_create = true) { + _getTopRightContainer(el, should_create = true, for_exp = false) { let cont = el._ffz_top_right ?? el.querySelector('.ffz-top-right'); if ( cont || ! should_create ) return cont; - const container = el.querySelector('a[data-a-target="preview-card-image-link"] > div'); + let container = for_exp + ? el.querySelector('.tw-aspect .tw-image') + : el.querySelector('a[data-a-target="preview-card-image-link"] > div'); + if ( container && for_exp ) + container = container.parentElement?.parentElement; if ( ! container ) return null; @@ -878,17 +909,17 @@ export default class Directory extends Module { } - updateUptime(el, props) { + updateUptime(el, props, for_exp = false) { if ( ! document.contains(el) ) return this.clearUptime(el); - const container = this._getTopRightContainer(el), - setting = this.settings.get('directory.uptime'); + const setting = this.settings.get('directory.uptime'), + container = this._getTopRightContainer(el, setting > 0, for_exp); //const container = el.querySelector('a[data-a-target="preview-card-image-link"] > div'), // setting = this.settings.get('directory.uptime'); - if ( ! container || setting === 0 || props.viewCount || props.animatedImageProps ) + if ( ! container || setting === 0 || props.viewCount || props.animatedImageProps || props.type === 'videos' || props.type === 'clips' ) return this.clearUptime(el); let created_at = props.createdAt; @@ -898,7 +929,7 @@ export default class Directory extends Module { el.ffz_stream_meta = null; this.twitch_data.getStreamMeta(null, props.channelLogin).then(data => { el.ffz_stream_meta = data; - this.updateUptime(el, props); + this.updateUptime(el, props, for_exp); }); } @@ -913,24 +944,27 @@ export default class Directory extends Module { const up_text = duration_to_string(uptime, false, false, false, setting === 1); - if ( ! el.ffz_uptime_el ) { + if ( ! el.ffz_uptime_el ) el.ffz_uptime_el = container.querySelector('.ffz-uptime-element'); - if ( ! el.ffz_uptime_el ) - container.appendChild(el.ffz_uptime_el = ( -
-
-
-
-
- {el.ffz_uptime_span =

} -

-
- {this.i18n.t('metadata.uptime.tooltip', 'Stream Uptime')} - {el.ffz_uptime_tt =
} + + if ( ! el.ffz_uptime_el ) + container.appendChild(el.ffz_uptime_el = ( +
+
+
+
+ {el.ffz_uptime_span =

}

- )); - } +
+ {this.i18n.t('metadata.uptime.tooltip', 'Stream Uptime')} + {el.ffz_uptime_tt =
} +
+
+ )); + + else if ( ! el.contains(el.ffz_uptime_el) ) + container.appendChild(el.ffz_uptime_el); if ( ! el.ffz_uptime_span ) el.ffz_uptime_span = el.ffz_uptime_el.querySelector('p'); @@ -943,7 +977,7 @@ export default class Directory extends Module { return this.clearUptime(el); if ( ! el.ffz_update_timer ) - el.ffz_update_timer = setInterval(this.updateUptime.bind(this, el, props), 1000); + el.ffz_update_timer = setInterval(this.updateUptime.bind(this, el, props, for_exp), 1000); el.ffz_uptime_span.textContent = up_text;