From 90d35ae6e5f014fc2d453fa7ecf167b961df73f3 Mon Sep 17 00:00:00 2001 From: KrX3D Date: Thu, 5 Mar 2026 18:32:20 +0100 Subject: [PATCH 1/3] add feature: hide tab buttons on library page --- mods/config.js | 1 + mods/features/adblock.js | 366 ++++++++++++++++--------------- mods/features/libraryTabHider.js | 65 ++++++ mods/ui/settings.js | 16 ++ 4 files changed, 269 insertions(+), 179 deletions(-) create mode 100644 mods/features/libraryTabHider.js diff --git a/mods/config.js b/mods/config.js index 48fd8f1c..4333647a 100644 --- a/mods/config.js +++ b/mods/config.js @@ -39,6 +39,7 @@ const defaultConfig = { enableHideWatchedVideos: false, hideWatchedVideosThreshold: 80, hideWatchedVideosPages: [], + hiddenLibraryTabIds: [], enableHideEndScreenCards: false, enableYouThereRenderer: true, lastAnnouncementCheck: 0, diff --git a/mods/features/adblock.js b/mods/features/adblock.js index d2574101..a77af75d 100644 --- a/mods/features/adblock.js +++ b/mods/features/adblock.js @@ -3,6 +3,7 @@ import Chapters from '../ui/chapters.js'; import resolveCommand from '../resolveCommand.js'; import { timelyAction, longPressData, MenuServiceItemRenderer, ShelfRenderer, TileRenderer, ButtonRenderer } from '../ui/ytUI.js'; import { PatchSettings } from '../ui/customYTSettings.js'; +import { applyLibraryTabHiding } from './libraryTabHider.js'; /** * This is a minimal reimplementation of the following uBlock Origin rule: @@ -16,221 +17,228 @@ import { PatchSettings } from '../ui/customYTSettings.js'; const origParse = JSON.parse; JSON.parse = function () { const r = origParse.apply(this, arguments); - const adBlockEnabled = configRead('enableAdBlock'); - const signinReminderEnabled = configRead('enableSigninReminder'); - if (r.adPlacements && adBlockEnabled) { - r.adPlacements = []; - } - - // Also set playerAds to false, just incase. - if (r.playerAds && adBlockEnabled) { - r.playerAds = false; - } + try { + const adBlockEnabled = configRead('enableAdBlock'); + const signinReminderEnabled = configRead('enableSigninReminder'); - // Also set adSlots to an empty array, emptying only the adPlacements won't work. - if (r.adSlots && adBlockEnabled) { - r.adSlots = []; - } + if (r.adPlacements && adBlockEnabled) { + r.adPlacements = []; + } - if (r.paidContentOverlay && !configRead('enablePaidPromotionOverlay')) { - r.paidContentOverlay = null; - } + // Also set playerAds to false, just incase. + if (r.playerAds && adBlockEnabled) { + r.playerAds = false; + } - if (r?.streamingData?.adaptiveFormats && configRead('videoPreferredCodec') !== 'any') { - const preferredCodec = configRead('videoPreferredCodec'); - const hasPreferredCodec = r.streamingData.adaptiveFormats.find(format => format.mimeType.includes(preferredCodec)); - if (hasPreferredCodec) { - r.streamingData.adaptiveFormats = r.streamingData.adaptiveFormats.filter(format => { - if (format.mimeType.startsWith('audio/')) return true; - return format.mimeType.includes(preferredCodec); - }); + // Also set adSlots to an empty array, emptying only the adPlacements won't work. + if (r.adSlots && adBlockEnabled) { + r.adSlots = []; } - } - // Drop "masthead" ad from home screen - if ( - r?.contents?.tvBrowseRenderer?.content?.tvSurfaceContentRenderer?.content - ?.sectionListRenderer?.contents - ) { - if (!signinReminderEnabled) { - r.contents.tvBrowseRenderer.content.tvSurfaceContentRenderer.content.sectionListRenderer.contents = - r.contents.tvBrowseRenderer.content.tvSurfaceContentRenderer.content.sectionListRenderer.contents.filter( - (elm) => !elm.feedNudgeRenderer - ); + if (r.paidContentOverlay && !configRead('enablePaidPromotionOverlay')) { + r.paidContentOverlay = null; } - if (adBlockEnabled) { - r.contents.tvBrowseRenderer.content.tvSurfaceContentRenderer.content.sectionListRenderer.contents = - r.contents.tvBrowseRenderer.content.tvSurfaceContentRenderer.content.sectionListRenderer.contents.filter( - (elm) => !elm.adSlotRenderer - ); - - for (const shelve of r.contents.tvBrowseRenderer.content.tvSurfaceContentRenderer.content.sectionListRenderer.contents) { - if (shelve.shelfRenderer) { - shelve.shelfRenderer.content.horizontalListRenderer.items = - shelve.shelfRenderer.content.horizontalListRenderer.items.filter( - (item) => !item.adSlotRenderer - ); - } + if (r?.streamingData?.adaptiveFormats && configRead('videoPreferredCodec') !== 'any') { + const preferredCodec = configRead('videoPreferredCodec'); + const hasPreferredCodec = r.streamingData.adaptiveFormats.find(format => format.mimeType.includes(preferredCodec)); + if (hasPreferredCodec) { + r.streamingData.adaptiveFormats = r.streamingData.adaptiveFormats.filter(format => { + if (format.mimeType.startsWith('audio/')) return true; + return format.mimeType.includes(preferredCodec); + }); } } - processShelves(r.contents.tvBrowseRenderer.content.tvSurfaceContentRenderer.content.sectionListRenderer.contents); - } + applyLibraryTabHiding(r); + + // Drop "masthead" ad from home screen + if ( + r?.contents?.tvBrowseRenderer?.content?.tvSurfaceContentRenderer?.content + ?.sectionListRenderer?.contents + ) { + if (!signinReminderEnabled) { + r.contents.tvBrowseRenderer.content.tvSurfaceContentRenderer.content.sectionListRenderer.contents = + r.contents.tvBrowseRenderer.content.tvSurfaceContentRenderer.content.sectionListRenderer.contents.filter( + (elm) => !elm.feedNudgeRenderer + ); + } - if (r.endscreen && configRead('enableHideEndScreenCards')) { - r.endscreen = null; - } + if (adBlockEnabled) { + r.contents.tvBrowseRenderer.content.tvSurfaceContentRenderer.content.sectionListRenderer.contents = + r.contents.tvBrowseRenderer.content.tvSurfaceContentRenderer.content.sectionListRenderer.contents.filter( + (elm) => !elm.adSlotRenderer + ); - if (r.messages && Array.isArray(r.messages) && !configRead('enableYouThereRenderer')) { - r.messages = r.messages.filter( - (msg) => !msg?.youThereRenderer - ); - } + for (const shelve of r.contents.tvBrowseRenderer.content.tvSurfaceContentRenderer.content.sectionListRenderer.contents) { + if (shelve.shelfRenderer) { + shelve.shelfRenderer.content.horizontalListRenderer.items = + shelve.shelfRenderer.content.horizontalListRenderer.items.filter( + (item) => !item.adSlotRenderer + ); + } + } + } - // Remove shorts ads - if (!Array.isArray(r) && r?.entries && adBlockEnabled) { - r.entries = r.entries?.filter( - (elm) => !elm?.command?.reelWatchEndpoint?.adClientParams?.isAd - ); - } + processShelves(r.contents.tvBrowseRenderer.content.tvSurfaceContentRenderer.content.sectionListRenderer.contents); + } - // Patch settings + if (r.endscreen && configRead('enableHideEndScreenCards')) { + r.endscreen = null; + } - if (r?.title?.runs) { - PatchSettings(r); - } + if (r.messages && Array.isArray(r.messages) && !configRead('enableYouThereRenderer')) { + r.messages = r.messages.filter( + (msg) => !msg?.youThereRenderer + ); + } - // DeArrow Implementation. I think this is the best way to do it. (DOM manipulation would be a pain) + // Remove shorts ads + if (!Array.isArray(r) && r?.entries && adBlockEnabled) { + r.entries = r.entries?.filter( + (elm) => !elm?.command?.reelWatchEndpoint?.adClientParams?.isAd + ); + } - if (r?.contents?.sectionListRenderer?.contents) { - processShelves(r.contents.sectionListRenderer.contents); - } + // Patch settings + + if (r?.title?.runs) { + PatchSettings(r); + } - if (r?.continuationContents?.sectionListContinuation?.contents) { - processShelves(r.continuationContents.sectionListContinuation.contents); - } + // DeArrow Implementation. I think this is the best way to do it. (DOM manipulation would be a pain) - if (r?.continuationContents?.horizontalListContinuation?.items) { - deArrowify(r.continuationContents.horizontalListContinuation.items); - hqify(r.continuationContents.horizontalListContinuation.items); - addLongPress(r.continuationContents.horizontalListContinuation.items); - r.continuationContents.horizontalListContinuation.items = hideVideo(r.continuationContents.horizontalListContinuation.items); - } - - if (r?.contents?.tvBrowseRenderer?.content?.tvSecondaryNavRenderer?.sections) { - for (const section of r.contents.tvBrowseRenderer.content.tvSecondaryNavRenderer.sections) { - for (const tab of section.tvSecondaryNavSectionRenderer.tabs) { - processShelves(tab.tabRenderer.content.tvSurfaceContentRenderer.content.sectionListRenderer.contents); - } + if (r?.contents?.sectionListRenderer?.contents) { + processShelves(r.contents.sectionListRenderer.contents); } - } - if (r?.contents?.singleColumnWatchNextResults?.pivot?.sectionListRenderer) { - if (!signinReminderEnabled) { - r.contents.singleColumnWatchNextResults.pivot.sectionListRenderer.contents = - r.contents.singleColumnWatchNextResults.pivot.sectionListRenderer.contents.filter( - (elm) => !elm.alertWithActionsRenderer - ); + if (r?.continuationContents?.sectionListContinuation?.contents) { + processShelves(r.continuationContents.sectionListContinuation.contents); } - processShelves(r.contents.singleColumnWatchNextResults.pivot.sectionListRenderer.contents, false); - if (window.queuedVideos.videos.length > 0) { - const queuedVideosClone = window.queuedVideos.videos.slice(); - queuedVideosClone.unshift(TileRenderer( - 'Clear Queue', - { - customAction: { - action: 'CLEAR_QUEUE' - } - })); - r.contents.singleColumnWatchNextResults.pivot.sectionListRenderer.contents.unshift(ShelfRenderer( - 'Queued Videos', - queuedVideosClone, - queuedVideosClone.findIndex(v => v.contentId === window.queuedVideos.lastVideoId) !== -1 ? - queuedVideosClone.findIndex(v => v.contentId === window.queuedVideos.lastVideoId) - : 0 - )); + + if (r?.continuationContents?.horizontalListContinuation?.items) { + deArrowify(r.continuationContents.horizontalListContinuation.items); + hqify(r.continuationContents.horizontalListContinuation.items); + addLongPress(r.continuationContents.horizontalListContinuation.items); + r.continuationContents.horizontalListContinuation.items = hideVideo(r.continuationContents.horizontalListContinuation.items); } - } - /* - - Chapters are disabled due to the API removing description data which was used to generate chapters - - if (r?.contents?.singleColumnWatchNextResults?.results?.results?.contents && configRead('enableChapters')) { - const chapterData = Chapters(r); - r.frameworkUpdates.entityBatchUpdate.mutations.push(chapterData); - resolveCommand({ - "clickTrackingParams": "null", - "loadMarkersCommand": { - "visibleOnLoadKeys": [ - chapterData.entityKey - ], - "entityKeys": [ - chapterData.entityKey - ] - } - }); - }*/ - - // Manual SponsorBlock Skips - - if (configRead('sponsorBlockManualSkips').length > 0 && r?.playerOverlays?.playerOverlayRenderer) { - const manualSkippedSegments = configRead('sponsorBlockManualSkips'); - let timelyActions = []; - if (window?.sponsorblock?.segments) { - for (const segment of window.sponsorblock.segments) { - if (manualSkippedSegments.includes(segment.category)) { - const timelyActionData = timelyAction( - `Skip ${segment.category}`, - 'SKIP_NEXT', - { - clickTrackingParams: null, - showEngagementPanelEndpoint: { - customAction: { - action: 'SKIP', - parameters: { - time: segment.segment[1] - } - } - } - }, - segment.segment[0] * 1000, - segment.segment[1] * 1000 - segment.segment[0] * 1000 - ); - timelyActions.push(timelyActionData); + + if (r?.contents?.tvBrowseRenderer?.content?.tvSecondaryNavRenderer?.sections) { + for (const section of r.contents.tvBrowseRenderer.content.tvSecondaryNavRenderer.sections) { + for (const tab of section.tvSecondaryNavSectionRenderer.tabs) { + processShelves(tab.tabRenderer.content.tvSurfaceContentRenderer.content.sectionListRenderer.contents); } } - r.playerOverlays.playerOverlayRenderer.timelyActionRenderers = timelyActions; } - } else if (r?.playerOverlays?.playerOverlayRenderer) { - r.playerOverlays.playerOverlayRenderer.timelyActionRenderers = []; - } - if (r?.transportControls?.transportControlsRenderer?.promotedActions && configRead('enableSponsorBlockHighlight')) { - if (window?.sponsorblock?.segments) { - const category = window.sponsorblock.segments.find(seg => seg.category === 'poi_highlight'); - if (category) { - r.transportControls.transportControlsRenderer.promotedActions.push({ - type: 'TRANSPORT_CONTROLS_BUTTON_TYPE_SPONSORBLOCK_HIGHLIGHT', - button: { - buttonRenderer: ButtonRenderer( - false, - 'Skip to highlight', + if (r?.contents?.singleColumnWatchNextResults?.pivot?.sectionListRenderer) { + if (!signinReminderEnabled) { + r.contents.singleColumnWatchNextResults.pivot.sectionListRenderer.contents = + r.contents.singleColumnWatchNextResults.pivot.sectionListRenderer.contents.filter( + (elm) => !elm.alertWithActionsRenderer + ); + } + processShelves(r.contents.singleColumnWatchNextResults.pivot.sectionListRenderer.contents, false); + if (window.queuedVideos.videos.length > 0) { + const queuedVideosClone = window.queuedVideos.videos.slice(); + queuedVideosClone.unshift(TileRenderer( + 'Clear Queue', + { + customAction: { + action: 'CLEAR_QUEUE' + } + })); + r.contents.singleColumnWatchNextResults.pivot.sectionListRenderer.contents.unshift(ShelfRenderer( + 'Queued Videos', + queuedVideosClone, + queuedVideosClone.findIndex(v => v.contentId === window.queuedVideos.lastVideoId) !== -1 ? + queuedVideosClone.findIndex(v => v.contentId === window.queuedVideos.lastVideoId) + : 0 + )); + } + } + /* + + Chapters are disabled due to the API removing description data which was used to generate chapters + + if (r?.contents?.singleColumnWatchNextResults?.results?.results?.contents && configRead('enableChapters')) { + const chapterData = Chapters(r); + r.frameworkUpdates.entityBatchUpdate.mutations.push(chapterData); + resolveCommand({ + "clickTrackingParams": "null", + "loadMarkersCommand": { + "visibleOnLoadKeys": [ + chapterData.entityKey + ], + "entityKeys": [ + chapterData.entityKey + ] + } + }); + }*/ + + // Manual SponsorBlock Skips + + if (configRead('sponsorBlockManualSkips').length > 0 && r?.playerOverlays?.playerOverlayRenderer) { + const manualSkippedSegments = configRead('sponsorBlockManualSkips'); + let timelyActions = []; + if (window?.sponsorblock?.segments) { + for (const segment of window.sponsorblock.segments) { + if (manualSkippedSegments.includes(segment.category)) { + const timelyActionData = timelyAction( + `Skip ${segment.category}`, 'SKIP_NEXT', { clickTrackingParams: null, - customAction: { - action: 'SKIP', - parameters: { - time: category.segment[0] + showEngagementPanelEndpoint: { + customAction: { + action: 'SKIP', + parameters: { + time: segment.segment[1] + } } } - }) + }, + segment.segment[0] * 1000, + segment.segment[1] * 1000 - segment.segment[0] * 1000 + ); + timelyActions.push(timelyActionData); } - }); + } + r.playerOverlays.playerOverlayRenderer.timelyActionRenderers = timelyActions; + } + } else if (r?.playerOverlays?.playerOverlayRenderer) { + r.playerOverlays.playerOverlayRenderer.timelyActionRenderers = []; + } + + if (r?.transportControls?.transportControlsRenderer?.promotedActions && configRead('enableSponsorBlockHighlight')) { + if (window?.sponsorblock?.segments) { + const category = window.sponsorblock.segments.find(seg => seg.category === 'poi_highlight'); + if (category) { + r.transportControls.transportControlsRenderer.promotedActions.push({ + type: 'TRANSPORT_CONTROLS_BUTTON_TYPE_SPONSORBLOCK_HIGHLIGHT', + button: { + buttonRenderer: ButtonRenderer( + false, + 'Skip to highlight', + 'SKIP_NEXT', + { + clickTrackingParams: null, + customAction: { + action: 'SKIP', + parameters: { + time: category.segment[0] + } + } + }) + } + }); + } } } + } catch (_) { + // Keep response unchanged if parsing hook hits an edge-case. } return r; diff --git a/mods/features/libraryTabHider.js b/mods/features/libraryTabHider.js new file mode 100644 index 00000000..926dc3aa --- /dev/null +++ b/mods/features/libraryTabHider.js @@ -0,0 +1,65 @@ +import { configRead } from '../config.js'; + +const getHiddenLibraryTabIds = () => { + const configured = configRead('hiddenLibraryTabIds'); + if (!Array.isArray(configured) || configured.length === 0) return new Set(); + return new Set(configured.map((id) => String(id || '').toLowerCase()).filter(Boolean)); +}; + +const matchesHiddenId = (value, hiddenIds) => { + const id = String(value || '').toLowerCase(); + if (!id) return false; + + for (const hiddenId of hiddenIds) { + if (id === hiddenId || id.includes(hiddenId)) return true; + } + + return false; +}; + +const shouldHideTabItem = (item, hiddenIds) => { + if (!item || typeof item !== 'object') return false; + + return matchesHiddenId(item?.tileRenderer?.contentId, hiddenIds) + || matchesHiddenId(item?.tabRenderer?.content?.tvSurfaceContentRenderer?.content?.gridRenderer?.items?.[0]?.tileRenderer?.contentId, hiddenIds) + || matchesHiddenId(item?.tabRenderer?.endpoint?.browseEndpoint?.browseId, hiddenIds) + || matchesHiddenId(item?.navigationEndpoint?.browseEndpoint?.browseId, hiddenIds) + || matchesHiddenId(item?.browseEndpoint?.browseId, hiddenIds); +}; + +const pruneLibraryTabs = (node, hiddenIds) => { + if (!node || typeof node !== 'object') return; + + if (Array.isArray(node?.horizontalListRenderer?.items)) { + node.horizontalListRenderer.items = node.horizontalListRenderer.items.filter((item) => !shouldHideTabItem(item, hiddenIds)); + } + + if (Array.isArray(node?.continuationContents?.horizontalListContinuation?.items)) { + node.continuationContents.horizontalListContinuation.items = + node.continuationContents.horizontalListContinuation.items.filter((item) => !shouldHideTabItem(item, hiddenIds)); + } + + if (Array.isArray(node?.tvSecondaryNavSectionRenderer?.tabs)) { + node.tvSecondaryNavSectionRenderer.tabs = node.tvSecondaryNavSectionRenderer.tabs.filter((tab) => !shouldHideTabItem(tab, hiddenIds)); + } + + for (const key of Object.keys(node)) { + const value = node[key]; + if (value && typeof value === 'object') { + if (Array.isArray(value)) { + for (const entry of value) { + if (entry && typeof entry === 'object') pruneLibraryTabs(entry, hiddenIds); + } + } else { + pruneLibraryTabs(value, hiddenIds); + } + } + } +}; + +export const applyLibraryTabHiding = (response) => { + const hiddenIds = getHiddenLibraryTabIds(); + if (hiddenIds.size === 0) return; + + pruneLibraryTabs(response, hiddenIds); +}; diff --git a/mods/ui/settings.js b/mods/ui/settings.js index 4337577c..f9467b01 100644 --- a/mods/ui/settings.js +++ b/mods/ui/settings.js @@ -504,6 +504,22 @@ export default function modernUI(update, parameters) { } ] }, + { + name: 'Library Tabs Buttons to Hide', + icon: 'TAB_LIBRARY', + value: null, + arrayToEdit: 'hiddenLibraryTabIds', + menuId: 'tt-hidden-library-tabs', + options: [ + { name: 'Music', value: 'femusic_last_played' }, + { name: 'Movies & Shows', value: 'festorefront' }, + { name: 'Podcasts', value: 'fecollection_podcasts' }, + { name: 'My Videos', value: 'femy_videos' }, + { name: 'History', value: 'fehistory' }, + { name: 'Watch Later', value: 'femy_youtube' }, + { name: 'Playlists', value: 'feplaylist_aggregation' } + ] + }, { name: 'Screen Dimming', icon: 'EYE_OFF', From 67b4591e5b97cd3f3127eddc87c6e7730ce04566 Mon Sep 17 00:00:00 2001 From: KrX3D Date: Thu, 5 Mar 2026 21:45:30 +0100 Subject: [PATCH 2/3] optimize --- mods/features/adblock.js | 5 ++++- mods/features/libraryTabHider.js | 9 +++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/mods/features/adblock.js b/mods/features/adblock.js index a77af75d..abd50165 100644 --- a/mods/features/adblock.js +++ b/mods/features/adblock.js @@ -51,7 +51,10 @@ JSON.parse = function () { } } - applyLibraryTabHiding(r); + const hiddenLibraryTabIds = configRead('hiddenLibraryTabIds'); + if (Array.isArray(hiddenLibraryTabIds) && hiddenLibraryTabIds.length > 0) { + applyLibraryTabHiding(r, hiddenLibraryTabIds); + } // Drop "masthead" ad from home screen if ( diff --git a/mods/features/libraryTabHider.js b/mods/features/libraryTabHider.js index 926dc3aa..12daabea 100644 --- a/mods/features/libraryTabHider.js +++ b/mods/features/libraryTabHider.js @@ -1,7 +1,4 @@ -import { configRead } from '../config.js'; - -const getHiddenLibraryTabIds = () => { - const configured = configRead('hiddenLibraryTabIds'); +const getHiddenLibraryTabIds = (configured) => { if (!Array.isArray(configured) || configured.length === 0) return new Set(); return new Set(configured.map((id) => String(id || '').toLowerCase()).filter(Boolean)); }; @@ -57,8 +54,8 @@ const pruneLibraryTabs = (node, hiddenIds) => { } }; -export const applyLibraryTabHiding = (response) => { - const hiddenIds = getHiddenLibraryTabIds(); +export const applyLibraryTabHiding = (response, configuredHiddenIds) => { + const hiddenIds = getHiddenLibraryTabIds(configuredHiddenIds); if (hiddenIds.size === 0) return; pruneLibraryTabs(response, hiddenIds); From b5662745d0a29f39d2ae8acc3b3e9ed101e4e54b Mon Sep 17 00:00:00 2001 From: KrX3D Date: Sun, 12 Apr 2026 20:49:08 +0200 Subject: [PATCH 3/3] Restore try/catch safety wrapper in JSON.parse hook Co-Authored-By: Claude Sonnet 4.6 --- mods/features/adblock.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/mods/features/adblock.js b/mods/features/adblock.js index 3036044e..acaf7e5e 100644 --- a/mods/features/adblock.js +++ b/mods/features/adblock.js @@ -18,6 +18,8 @@ import { applyLibraryTabHiding } from './libraryTabHider.js'; const origParse = JSON.parse; JSON.parse = function () { const r = origParse.apply(this, arguments); + + try { const adBlockEnabled = configRead('enableAdBlock'); const signinReminderEnabled = configRead('enableSigninReminder'); @@ -257,6 +259,10 @@ JSON.parse = function () { } } + } catch (_) { + // Keep response unchanged if parsing hook hits an edge-case. + } + return r; };