YouTube Auto Redirect & Theater Mode + Sub Count

Redirect channel root/featured to /videos, auto-enable theater mode, and show total subscriptions count (top + bottom)

目前為 2025-09-10 提交的版本,檢視 最新版本

// ==UserScript==
// @name         YouTube Auto Redirect & Theater Mode + Sub Count
// @version      2.5
// @description  Redirect channel root/featured to /videos, auto-enable theater mode, and show total subscriptions count (top + bottom)
// @match        https://www.youtube.com/*
// @run-at       document-end
// @grant        none
// @namespace https://gf.qytechs.cn/users/1513610
// ==/UserScript==

/*
 * YouTube Script Functionality:
 * 1. Auto-redirects channel pages (@username or @username/featured) to /videos
 * 2. Automatically enables theater mode on watch pages
 * 3. Counts and displays total subscriptions in /feed/channels
 * 4. Features:
 *    - Configurable settings (theater mode, sub count display)
 *    - Accessibility support (reduced motion, ARIA)
 *    - Persistent settings via localStorage
 *    - Visual feedback and error handling
 */

(function () {
    "use strict";

    // Configuration defaults
    const DEFAULT_CONFIG = {
        theaterMode: true,
        showSubCount: true,
        scrollDelay: 1000,
        maxScrollAttempts: 20,
        reducedMotion: false,
        bannerStyle: {
            fontSize: "18px",
            fontWeight: "bold",
            padding: "10px",
            color: "#fff",
            background: "#c00",
            margin: "10px 0",
            borderRadius: "8px",
            textAlign: "center"
        }
    };

    // Load or initialize config
    let config = JSON.parse(localStorage.getItem("ytScriptConfig")) || DEFAULT_CONFIG;

    // Save config to localStorage
    function saveConfig() {
        localStorage.setItem("ytScriptConfig", JSON.stringify(config));
    }

    const channelRegex = /^https:\/\/www\.youtube\.com\/@[\w-]+(?:\/featured)?\/?$/;

    function enableTheater() {
        if (!config.theaterMode) return false;
        try {
            const btn = document.querySelector('button[data-tooltip-title="Theater mode (t)"]');
            if (btn) {
                btn.click();
                console.log("✅ Theater mode enabled.");
                return true;
            }
            return false;
        } catch (error) {
            console.error("⚠️ Failed to enable theater mode:", error);
            return false;
        }
    }

    function handleUrl(url) {
        try {
            if (channelRegex.test(url)) {
                location.replace(url.replace(/\/(featured)?\/?$/, "") + "/videos");
                return;
            }
            if (url.includes("/watch")) {
                let tries = 0;
                const iv = setInterval(() => {
                    try {
                        if (config.theaterMode && enableTheater() || ++tries > 15) clearInterval(iv);
                    } catch (error) {
                        console.error("⚠️ Theater mode attempt failed:", error);
                        if (tries > 15) clearInterval(iv);
                    }
                }, 700);
            }
            if (url.includes("/feed/channels")) {
                if (config.showSubCount) countSubscriptions().catch(e => console.error("⚠️ Subscription count failed:", e));
            }
        } catch (error) {
            console.error("⚠️ URL handling failed:", error);
        }
    }

    async function countSubscriptions() {
        try {
            // Auto-scroll until no new content is loaded
            let lastHeight = 0;
            while (true) {
                window.scrollTo(0, document.documentElement.scrollHeight);
                await new Promise(r => setTimeout(r, 1000));
                let newHeight = document.documentElement.scrollHeight;
                if (newHeight === lastHeight) break;
                lastHeight = newHeight;
            }

            // Count channel items
            const channels = document.querySelectorAll("ytd-channel-renderer, ytd-grid-channel-renderer");
            const count = channels.length;

            // Reusable banner
            function makeBanner(id) {
                const div = document.createElement("div");
                div.id = id;
                div.textContent = `📺 Subscribed Channels: ${count}`;
                div.style.cssText = "font-size:18px;font-weight:bold;padding:10px;color:#fff;background:#c00;margin:10px 0;border-radius:8px;text-align:center;";
                return div;
            }

            const container = document.querySelector("ytd-section-list-renderer") || document.body;

            // Top banner
            if (!document.getElementById("sub-count-top")) {
                container.prepend(makeBanner("sub-count-top"));
            }

            // Bottom banner
            if (!document.getElementById("sub-count-bottom")) {
                container.append(makeBanner("sub-count-bottom"));
                window.scrollTo(0, document.documentElement.scrollHeight);
            }
        } catch (error) {
            console.error("⚠️ Failed to count subscriptions:", error);
            const errorBanner = document.createElement("div");
            errorBanner.textContent = "⚠️ Failed to load subscription count";
            errorBanner.style.cssText = "font-size:18px;font-weight:bold;padding:10px;color:#fff;background:#900;margin:10px 0;border-radius:8px;text-align:center;";
            (document.querySelector("ytd-section-list-renderer") || document.body).prepend(errorBanner);
        }
    }

    function updateBanners(count) {
        // Update or create top banner
        const updateOrCreateBanner = (id) => {
            let banner = document.getElementById(id);
            if (!banner) {
                banner = document.createElement("div");
                banner.id = id;
                banner.setAttribute("role", "status");
                banner.setAttribute("aria-live", "polite");
                banner.style.cssText = Object.entries(config.bannerStyle)
                    .map(([key, value]) => `${key}:${value}`)
                    .join(';');

                const container = document.querySelector("ytd-section-list-renderer, ytd-browse") || document.body;
                id.includes('top') ? container.prepend(banner) : container.append(banner);
            }
            banner.textContent = `📺 Subscribed Channels: ${count}`;
        };

        updateOrCreateBanner("sub-count-top");
        updateOrCreateBanner("sub-count-bottom");
    }

    function createErrorBanner() {
        const errorBanner = document.createElement("div");
        errorBanner.textContent = "⚠️ Failed to load subscription count";
        errorBanner.style.cssText = "font-size:18px;font-weight:bold;padding:10px;color:#fff;background:#900;margin:10px 0;border-radius:8px;text-align:center;";
        (document.querySelector("ytd-section-list-renderer, ytd-browse") || document.body).prepend(errorBanner);
    }

    // Save scroll position before navigation
    let lastScrollPosition = 0;
    document.addEventListener('scroll', () => {
        lastScrollPosition = window.scrollY;
    });

    // Restore scroll position after navigation
    window.addEventListener('load', () => {
        if (lastScrollPosition > 0) {
            window.scrollTo(0, lastScrollPosition);
        }
    });

    // Wait until everything is loaded
    try {
        window.addEventListener("load", () => {
            try {
                // detect SPA navigations
                let lastUrl = location.href;
                new MutationObserver(() => {
                    try {
                        if (location.href !== lastUrl) {
                            lastUrl = location.href;
                            handleUrl(lastUrl);
                        }
                    } catch (observerError) {
                        console.error("⚠️ URL observer failed:", observerError);
                    }
                }).observe(document, { childList: true, subtree: true });

                // initial
                handleUrl(location.href);
            } catch (loadError) {
                console.error("⚠️ Initial load failed:", loadError);
            }
        });
    } catch (initError) {
        console.error("⚠️ Script initialization failed:", initError);
    }
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址