YouTube Auto Redirect & Theater Mode + Sub Count + UnShort Shorts

Redirect channel root/featured to /videos, auto-enable theater mode, show subscription count, and redirect Shorts to full watch view

当前为 2025-09-24 提交的版本,查看 最新版本

// ==UserScript==
// @name         YouTube Auto Redirect & Theater Mode + Sub Count + UnShort Shorts
// @version      3.5
// @description  Redirect channel root/featured to /videos, auto-enable theater mode, show subscription count, and redirect Shorts to full watch view
// @match        https://www.youtube.com/*
// @run-at       document-start
// @grant        none
// @namespace https://gf.qytechs.cn/users/1513610
// ==/UserScript==


(function () {
    "use strict";

    // ======================
    // CONFIGURATION
    // ======================
    const DEFAULT_CONFIG = {
        theaterMode: true,
        showSubCount: true,
        scrollDelay: 1200, // Increased delay for more reliable content loading
        maxScrollAttempts: 50, // Increased attempts for users with many subscriptions
        reducedMotion: window.matchMedia('(prefers-reduced-motion: reduce)').matches,
        bannerStyle: {
            fontSize: "16px",
            fontWeight: "bold",
            padding: "12px",
            color: "#fff",
            background: "rgba(204, 0, 0, 0.9)",
            margin: "10px 0",
            borderRadius: "8px",
            textAlign: "center",
            zIndex: "1000",
            position: "relative"
        }
    };
    let config = JSON.parse(localStorage.getItem("ytScriptConfig")) || DEFAULT_CONFIG;
    const channelRegex = /^https:\/\/www\.youtube\.com\/@[\w.-]+(?:\/featured)?\/?$/;
    const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));

    // ======================
    // FEATURE: SHORTS REDIRECT
    // ======================
    function redirectShorts(url) {
        if (url.includes("/shorts/")) {
            const videoId = url.split("/shorts/").pop().split(/[?&]/)[0];
            if (videoId) {
                location.replace(`https://www.youtube.com/watch?v=${videoId}`);
            }
        }
    }

    // ======================
    // FEATURE: THEATER MODE
    // ======================
    function enableTheater() {
        if (!config.theaterMode || document.querySelector("ytd-watch-flexy[theater]")) return;
        try {
            const theaterButton = document.querySelector("button.ytp-size-button");
            if (theaterButton) {
                theaterButton.click();
                console.log("✅ Theater mode enabled.");
            }
        } catch (error) {
            console.error("⚠️ Failed to enable theater mode:", error);
        }
    }

    // ======================
    // FEATURE: REVERT DUBBED AUDIO
    // ======================
    async function revertToOriginalAudio() {
        try {
            const settingsButton = document.querySelector("button.ytp-settings-button");
            if (!settingsButton) return;

            settingsButton.click();
            await sleep(200);

            const menuItems = document.querySelectorAll(".ytp-menuitem-label");
            const audioTrackButton = Array.from(menuItems).find(el => el.innerText.includes("Audio track"));

            if (!audioTrackButton) {
                return settingsButton.click(); // Close menu if no audio track option
            }

            audioTrackButton.click();
            await sleep(200);

            const audioOptions = document.querySelectorAll(".ytp-menuitem-label");
            const originalOption = Array.from(audioOptions).find(el => el.innerText.toLowerCase().includes("(original)"));

            if (originalOption && !originalOption.closest('.ytp-menuitem[aria-checked="true"]')) {
                originalOption.click();
                console.log("✅ Audio reverted to original track.");
            } else {
                // Close menu if original is already selected or not found
                if (document.querySelector('.ytp-panel-menu')) settingsButton.click();
            }
        } catch (error) {
            console.error("⚠️ Audio revert failed:", error);
            // Ensure menu is closed on error
            if (document.querySelector('.ytp-panel-menu')) document.querySelector("button.ytp-settings-button")?.click();
        }
    }

    // ======================
    // FEATURE: COUNT SUBSCRIPTIONS (IMPROVED)
    // ======================
    async function countSubscriptions() {
        // 1. Create and show a temporary loading banner for user feedback
        const bannerCss = Object.entries(config.bannerStyle).map(([k, v]) => `${k.replace(/([A-Z])/g, '-$1').toLowerCase()}:${v}`).join(';');
        const tempBanner = document.createElement("div");
        tempBanner.id = "sub-count-loading";
        tempBanner.textContent = "⏳ Loading all channels, please wait...";
        tempBanner.style.cssText = bannerCss;
        const container = document.querySelector("ytd-section-list-renderer, ytd-browse") || document.body;
        container.prepend(tempBanner);

        // 2. Aggressively scroll down until no more new channels load
        let lastHeight = 0;
        for (let i = 0; i < config.maxScrollAttempts; i++) {
            window.scrollTo(0, document.documentElement.scrollHeight);
            await sleep(config.scrollDelay);
            if (document.documentElement.scrollHeight === lastHeight) break; // Stop when scroll height doesn't change
            lastHeight = document.documentElement.scrollHeight;
        }

        // 3. Remove loading banner and display final count
        tempBanner.remove();
        const channels = document.querySelectorAll("ytd-channel-renderer, ytd-grid-channel-renderer");
        updateBanners(channels.length, bannerCss, container);

        // 4. Scroll to the bottom to reveal the bottom banner
        await sleep(100);
        window.scrollTo({
            top: document.documentElement.scrollHeight,
            behavior: config.reducedMotion ? 'auto' : 'smooth'
        });
    }

    function updateBanners(count, bannerCss, container) {
        const createOrUpdate = (id, prepend = false) => {
            let banner = document.getElementById(id);
            if (!banner) {
                banner = document.createElement("div");
                banner.id = id;
                banner.setAttribute("role", "status");
                banner.setAttribute("aria-live", "polite");
                container[prepend ? 'prepend' : 'append'](banner);
            }
            banner.style.cssText = bannerCss;
            banner.textContent = `📺 Subscribed Channels: ${count}`;
        };
        createOrUpdate("sub-count-top", true);
        createOrUpdate("sub-count-bottom");
    }

    // ======================
    // URL AND NAVIGATION HANDLER
    // ======================
    function handleUrl(url) {
        if (url.includes("/shorts/")) {
            redirectShorts(url);
        } else if (channelRegex.test(url)) {
            location.replace(`${url.replace(/\/featured\/?$|$/, "")}/videos`);
        } else if (url.includes("/watch")) {
            // Use a short delay to ensure the player is ready
            setTimeout(() => {
                enableTheater();
                revertToOriginalAudio();
            }, 1000);
        } else if (url.includes("/feed/channels") && config.showSubCount) {
            countSubscriptions().catch(e => console.error("⚠️ Subscription count failed:", e));
        }
    }

    // Use YouTube's custom navigation event for SPAs
    window.addEventListener("yt-navigate-finish", () => {
        handleUrl(location.href);
        const scrollPos = sessionStorage.getItem('ytScrollPos');
        if (scrollPos && location.href.includes('/feed/channels')) {
            window.scrollTo(0, parseInt(scrollPos, 10));
        }
    });

    // Handle initial page load
    handleUrl(location.href);

    // Persist scroll position within the session
    window.addEventListener('scroll', () => sessionStorage.setItem('ytScrollPos', window.scrollY), { passive: true });

})();

QingJ © 2025

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