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();
})();