Youtube Logo - Link to subscriptions feed

Change YouTube logo link to user's subscription feed instead of homepage, when logged in, for 2017 and later YouTube layout.

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name            Youtube Logo - Link to subscriptions feed
// @namespace       Youtube Logo - Link to subscriptions feed
// @description     Change YouTube logo link to user's subscription feed instead of homepage, when logged in, for 2017 and later YouTube layout.
// @version         2.1.5
// @include         *://*.youtube.tld/*
// @supportURL      https://greasyfork.org/en/scripts/13582/feedback
// @author          aciid
// @license         MIT
// @run-at          document-start
// ==/UserScript==

(function() {
    "use strict";

    var SUBSCRIPTIONS_PATH = "/feed/subscriptions";
    var SUBSCRIPTIONS_BROWSE_ID = "FEsubscriptions";
    var DEFAULT_SUBSCRIPTIONS_NAVIGATION_ENDPOINT = {
        clickTrackingParams: "",
        commandMetadata: {
            webCommandMetadata: {
                url: SUBSCRIPTIONS_PATH,
                webPageType: "WEB_PAGE_TYPE_BROWSE",
                rootVe: 96368,
                apiUrl: "/youtubei/v1/browse"
            }
        },
        browseEndpoint: {
            browseId: SUBSCRIPTIONS_BROWSE_ID
        }
    };
    var LOGO_SELECTOR = "a#logo[href]";
    var SUBSCRIPTIONS_ENDPOINT_SELECTOR = "a#endpoint[href]";
    var ORIGINAL_HREF_ATTRIBUTE = "data-ytltsf-original-href";
    var updateScheduled = false;
    var observedMasthead = null;
    var cachedSubscriptionsNavigationEndpoint = null;
    var bootstrapObserver = new MutationObserver(ScheduleUpdate);
    var mastheadObserver = new MutationObserver(ScheduleUpdate);

    function NormalizePath(pathname) {
        return pathname.replace(/\/+$/, "") || "/";
    }

    function IsSubscriptionsUrl(url) {
        try {
            return NormalizePath(new URL(url, location.origin).pathname) === SUBSCRIPTIONS_PATH;
        } catch (error) {
            return false;
        }
    }

    function IsLoggedIn() {
        return document.querySelector("button#avatar-btn") !== null;
    }

    function IsTopbarLogo(node) {
        return node instanceof HTMLAnchorElement &&
            node.id === "logo" &&
            node.closest("ytd-masthead, ytd-topbar-logo-renderer, ytd-yoodle-renderer") !== null;
    }

    function FindLogoFromEvent(event) {
        if (typeof event.composedPath === "function") {
            var path = event.composedPath();
            for (var i = 0; i < path.length; i++) {
                var node = path[i];
                if (IsTopbarLogo(node)) {
                    return node;
                }
            }
        }

        if (event.target instanceof Element) {
            var logo = event.target.closest(LOGO_SELECTOR);
            if (IsTopbarLogo(logo)) {
                return logo;
            }
        }

        return null;
    }

    function IsPlainLeftClick(event) {
        return event.button === 0 &&
            !event.defaultPrevented &&
            !event.altKey &&
            !event.ctrlKey &&
            !event.metaKey &&
            !event.shiftKey;
    }

    function FindSubscriptionsEndpoint() {
        var endpoints = document.querySelectorAll(SUBSCRIPTIONS_ENDPOINT_SELECTOR);

        for (var i = 0; i < endpoints.length; i++) {
            if (endpoints[i] instanceof HTMLAnchorElement && IsSubscriptionsUrl(endpoints[i].href)) {
                return endpoints[i];
            }
        }

        return null;
    }

    function DeepClone(value) {
        if (typeof structuredClone === "function") {
            return structuredClone(value);
        }

        return JSON.parse(JSON.stringify(value));
    }

    function GetRendererData(element) {
        if (element === null || element === undefined) {
            return null;
        }

        return element.data || element.polymerController?.data || element.inst?.data || null;
    }

    function FindSubscriptionsNavigationEndpointInData() {
        var renderers = document.querySelectorAll("ytd-guide-entry-renderer, ytd-guide-collapsible-section-entry-renderer, ytd-mini-guide-entry-renderer");

        for (var i = 0; i < renderers.length; i++) {
            var data = GetRendererData(renderers[i]);
            var endpoint = data?.navigationEndpoint || data?.endpoint || null;

            if (endpoint?.browseEndpoint?.browseId === SUBSCRIPTIONS_BROWSE_ID ||
                NormalizePath(endpoint?.commandMetadata?.webCommandMetadata?.url || "") === SUBSCRIPTIONS_PATH) {
                return endpoint;
            }
        }

        return null;
    }

    function GetSubscriptionsNavigationEndpoint() {
        var discoveredEndpoint = FindSubscriptionsNavigationEndpointInData();

        if (discoveredEndpoint !== null) {
            cachedSubscriptionsNavigationEndpoint = DeepClone(discoveredEndpoint);
        }

        return DeepClone(cachedSubscriptionsNavigationEndpoint || DEFAULT_SUBSCRIPTIONS_NAVIGATION_ENDPOINT);
    }

    function GetNavigationTargets() {
        var app = document.querySelector("ytd-app");
        var targets = [app];

        if (app !== null) {
            targets.push(app.polymerController, app.inst);
        }

        return targets.filter(function(target, index) {
            return target !== null && target !== undefined && targets.indexOf(target) === index;
        });
    }

    function HandleSubscriptionsNavigationWithApp() {
        var targets = GetNavigationTargets();
        var endpoint = GetSubscriptionsNavigationEndpoint();
        var requestFactories = [
            function() {
                return {
                    type: 0,
                    command: DeepClone(endpoint)
                };
            },
            function() {
                return {
                    type: 0,
                    endpoint: DeepClone(endpoint),
                    command: DeepClone(endpoint)
                };
            }
        ];

        for (var i = 0; i < targets.length; i++) {
            var target = targets[i];

            for (var j = 0; j < requestFactories.length; j++) {
                try {
                    if (typeof target.handleNavigate === "function") {
                        target.handleNavigate(requestFactories[j]());
                        return true;
                    }
                } catch (error) {
                    // Try the next request shape or target.
                }
            }
        }

        return false;
    }

    function NavigateToSubscriptions(attempt) {
        var endpoint = FindSubscriptionsEndpoint();

        if (endpoint !== null) {
            endpoint.click();
            return;
        }

        if (attempt === 0 || attempt === 10) {
            HandleSubscriptionsNavigationWithApp();
        }

        if (attempt < 20 && NormalizePath(location.pathname) !== SUBSCRIPTIONS_PATH) {
            requestAnimationFrame(function() {
                NavigateToSubscriptions(attempt + 1);
            });
        }
    }

    function UpdateLogoHref(logo, loggedIn) {
        if (!logo.hasAttribute(ORIGINAL_HREF_ATTRIBUTE)) {
            var originalHref = logo.getAttribute("href");

            if (originalHref !== null) {
                logo.setAttribute(ORIGINAL_HREF_ATTRIBUTE, originalHref);
            }
        }

        if (loggedIn) {
            if (!IsSubscriptionsUrl(logo.getAttribute("href") || "")) {
                logo.setAttribute("href", SUBSCRIPTIONS_PATH);
            }

            return;
        }

        if (logo.hasAttribute(ORIGINAL_HREF_ATTRIBUTE)) {
            logo.setAttribute("href", logo.getAttribute(ORIGINAL_HREF_ATTRIBUTE));
        }
    }

    function UpdateLogos() {
        updateScheduled = false;
        RefreshObservedMasthead();
        GetSubscriptionsNavigationEndpoint();

        var logos = Array.from(document.querySelectorAll(LOGO_SELECTOR)).filter(IsTopbarLogo);
        if (logos.length === 0) {
            return;
        }

        var loggedIn = IsLoggedIn();

        logos.forEach(function(logo) {
            UpdateLogoHref(logo, loggedIn);
        });
    }

    function RefreshObservedMasthead() {
        var nextMasthead = document.querySelector("#masthead-container") || document.querySelector("ytd-masthead");

        if (nextMasthead === observedMasthead) {
            return;
        }

        mastheadObserver.disconnect();
        observedMasthead = nextMasthead;

        if (observedMasthead !== null) {
            bootstrapObserver.disconnect();
            mastheadObserver.observe(observedMasthead, {
                childList: true,
                subtree: true
            });
        }
    }

    function ScheduleUpdate() {
        if (updateScheduled) {
            return;
        }

        updateScheduled = true;
        requestAnimationFrame(UpdateLogos);
    }

    function OnDocumentClick(event) {
        var logo = FindLogoFromEvent(event);

        if (logo === null || !IsLoggedIn() || !IsPlainLeftClick(event)) {
            return;
        }

        event.preventDefault();
        event.stopImmediatePropagation();
        NavigateToSubscriptions(0);
    }

    document.addEventListener("click", OnDocumentClick, true);
    document.addEventListener("DOMContentLoaded", ScheduleUpdate, { once: true });

    ["yt-navigate-finish", "yt-page-data-updated"].forEach(function(eventName) {
        document.addEventListener(eventName, ScheduleUpdate, true);
        window.addEventListener(eventName, ScheduleUpdate, true);
    });

    bootstrapObserver.observe(document.documentElement, {
        childList: true,
        subtree: true
    });

    ScheduleUpdate();
})();