Automatically expands the "Show more" link in the left sidebar Subscriptions on YouTube, and keeps it expanded across navigations.
// ==UserScript==
// @name YouTube Always Show More (Subscriptions)
// @namespace https://yourscripts.example
// @version 1.2.0
// @license MIT
// @description Automatically expands the "Show more" link in the left sidebar Subscriptions on YouTube, and keeps it expanded across navigations.
// @author ezzdev
// @match https://www.youtube.com/*
// @run-at document-idle
// @grant none
// ==/UserScript==
(function() {
'use strict';
// Debounce helper
const debounce = (fn, wait = 200) => {
let t;
return (...args) => {
clearTimeout(t);
t = setTimeout(() => fn(...args), wait);
};
};
// Utility: find potential "Show more" toggles inside the left guide
function findShowMoreButtons(guideRoot) {
if (!guideRoot) return [];
const candidates = [];
// 1) Buttons/controls that are collapsed via aria-expanded
candidates.push(...guideRoot.querySelectorAll(
'button[aria-expanded="false"], [role="button"][aria-expanded="false"]'
));
// 2) Buttons with an aria-label or title that mentions "Show more" (case-insensitive)
candidates.push(...Array.from(guideRoot.querySelectorAll('button, [role="button"], yt-button-shape, tp-yt-paper-item')).filter(el => {
const label = (el.getAttribute?.('aria-label') || el.getAttribute?.('title') || '').trim().toLowerCase();
return label.includes('show more') || label.includes('more');
}));
// 3) Fallback: elements whose visible text says "Show more" (case-insensitive)
candidates.push(...Array.from(guideRoot.querySelectorAll('button, [role="button"], yt-button-shape, tp-yt-paper-item')).filter(el => {
const text = (el.textContent || '').trim().toLowerCase();
// keep it loose but not too loose
return text === 'show more' || text === 'more' || text.startsWith('show more');
}));
// Only keep ones that are actually visible
const visible = candidates.filter(el => {
const rect = el.getBoundingClientRect?.();
const style = window.getComputedStyle?.(el);
return rect && rect.width > 0 && rect.height > 0 && style && style.visibility !== 'hidden' && style.display !== 'none';
});
// Dedupe
return Array.from(new Set(visible));
}
// Try to identify the left guide root (desktop #guide; fallback to drawer guide)
function getGuideRoot() {
return document.querySelector('#guide') ||
document.querySelector('ytd-guide-renderer') ||
document.querySelector('ytd-mini-guide-renderer') ||
null;
}
let lastClickTs = 0;
function ensureExpanded() {
const now = Date.now();
// throttle: no more than once every 500ms
if (now - lastClickTs < 500) return;
const guideRoot = getGuideRoot();
if (!guideRoot) return;
// We only want the Subscriptions "Show more" in the left guide.
// Click any collapsed toggles we can find; usually there is only one relevant.
const buttons = findShowMoreButtons(guideRoot);
// Prefer ones that live inside a Subscriptions section if we can spot it
const prioritized = buttons.sort((a, b) => {
const aScore = scoreButton(a);
const bScore = scoreButton(b);
return bScore - aScore; // higher score first
});
for (const btn of prioritized) {
try {
btn.click();
lastClickTs = Date.now();
// Small break in case there are multiple collapses
break;
} catch (_) {}
}
}
// Heuristic: reward buttons near text that says "Subscriptions" (any language-safe-ish hint)
function scoreButton(el) {
let score = 0;
// aria-expanded false is a good sign
if (el.getAttribute('aria-expanded') === 'false') score += 3;
// Is it in the guide?
if (el.closest('#guide, ytd-guide-renderer, ytd-mini-guide-renderer')) score += 2;
// Nearby text “Subscriptions”
const nearbyText = (el.closest('ytd-guide-section-renderer') || el.parentElement)?.textContent?.toLowerCase() || '';
if (/\bsubscription/.test(nearbyText)) score += 3;
// Label hints
const label = (el.getAttribute('aria-label') || el.getAttribute('title') || el.textContent || '').toLowerCase();
if (label.includes('show more')) score += 2;
if (label === 'more' || label.includes('more')) score += 1;
return score;
}
// Observe changes in the guide so we can re-expand when YouTube re-renders
const observeGuide = debounce(() => {
const guideRoot = getGuideRoot();
if (!guideRoot) return;
ensureExpanded();
// MutationObserver to catch SPA updates or reflows of the guide section
const mo = new MutationObserver(debounce(() => {
ensureExpanded();
}, 200));
mo.observe(guideRoot, { childList: true, subtree: true });
}, 250);
// Hook into YouTube’s SPA navigation events if present
window.addEventListener('yt-navigate-finish', () => {
observeGuide();
// Run a few times after navigation just in case
setTimeout(ensureExpanded, 200);
setTimeout(ensureExpanded, 600);
setTimeout(ensureExpanded, 1200);
}, { passive: true });
// Initial attempts after page ready
const kickOff = () => {
observeGuide();
// Try a few times early in case the guide loads late
let tries = 0;
const t = setInterval(() => {
ensureExpanded();
tries++;
if (tries >= 10) clearInterval(t);
}, 400);
};
if (document.readyState === 'complete' || document.readyState === 'interactive') {
kickOff();
} else {
window.addEventListener('DOMContentLoaded', kickOff, { once: true });
}
})();