YouTube Enhancer (Secret Stats)

Integrating "Secret Stats" and "Stream Stats" buttons into the channel page, directing users to detailed analytics pages for insights into the channel.

As of 19.10.2024. See ბოლო ვერსია.

// ==UserScript==
// @name         YouTube Enhancer (Secret Stats)
// @description  Integrating "Secret Stats" and "Stream Stats" buttons into the channel page, directing users to detailed analytics pages for insights into the channel.
// @icon         https://raw.githubusercontent.com/exyezed/youtube-enhancer/refs/heads/main/extras/youtube-enhancer.png
// @version      1.0
// @author       exyezed
// @namespace    https://github.com/exyezed/youtube-enhancer/
// @supportURL   https://github.com/exyezed/youtube-enhancer/issues
// @license      MIT
// @match        https://www.youtube.com/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // Button Creation and Manipulation
    function createButton(text, iconName, id) {
        const button = document.createElement('button');
        button.id = id;
        button.className = 'yt-spec-button-shape-next yt-spec-button-shape-next--tonal yt-spec-button-shape-next--mono yt-spec-button-shape-next--size-m';
        button.style.display = 'inline-flex';
        button.style.alignItems = 'center';
        button.style.justifyContent = 'center';
        button.style.minWidth = 'auto';
        button.style.padding = '0 16px';
        button.style.height = '36px';
        button.style.fontSize = '14px';
        button.style.lineHeight = '36px';
        button.style.fontWeight = '500';

        const icon = document.createElement('span');
        icon.className = 'material-symbols-outlined';
        icon.textContent = iconName;
        icon.style.marginRight = '4px';
        icon.style.fontSize = '24px';
        icon.style.fontWeight = '100';

        const buttonText = document.createTextNode(text);

        button.appendChild(icon);
        button.appendChild(buttonText);

        return button;
    }

    function createButtonWrapper(button) {
        const buttonWrapper = document.createElement('div');
        buttonWrapper.className = 'yt-flexible-actions-view-model-wiz__action';
        buttonWrapper.appendChild(button);
        return buttonWrapper;
    }

    function addStatsButtons() {
        const targetElement = document.querySelector('yt-flexible-actions-view-model');
        const lastAction = targetElement ? targetElement.querySelector('div.yt-flexible-actions-view-model-wiz__action:last-child') : null;

        if (lastAction && !document.querySelector('#YouTubeEnhancerChannelSecretStats')) {
            const secretStatsButton = createButton('Secret Stats', 'lock_open', 'YouTubeEnhancerChannelSecretStats');
            const streamStatsButton = createButton('Stream Stats', 'cell_tower', 'YouTubeEnhancerStreamStats');

            secretStatsButton.addEventListener('click', function() {
                const channelIdentifier = getChannelIdentifier();
                if (channelIdentifier) {
                    const statsUrl = `https://exyezed.vercel.app/stats/secret/${channelIdentifier}`;
                    window.open(statsUrl, '_blank');
                } else {
                    alert('Could not determine channel identifier. Please try again on a channel page.');
                }
            });

            streamStatsButton.addEventListener('click', function() {
                const channelIdentifier = getChannelIdentifier();
                if (channelIdentifier) {
                    const statsUrl = `https://exyezed.vercel.app/stats/stream/${channelIdentifier}`;
                    window.open(statsUrl, '_blank');
                } else {
                    alert('Could not determine channel identifier. Please try again on a channel page.');
                }
            });

            const secretStatsWrapper = createButtonWrapper(secretStatsButton);
            const streamStatsWrapper = createButtonWrapper(streamStatsButton);

            lastAction.insertAdjacentElement('afterend', streamStatsWrapper);
            lastAction.insertAdjacentElement('afterend', secretStatsWrapper);
        }
    }

    // Channel Identifier Functions
    function getChannelIdentifier() {
        const channelId = getChannelId();
        if (channelId) return channelId;
        return getChannelHandle();
    }

    function getChannelId() {
        const channelIdMeta = document.querySelector('meta[itemprop="channelId"]');
        if (channelIdMeta) {
            return channelIdMeta.content;
        }

        const urlParams = new URLSearchParams(window.location.search);
        return urlParams.get('channel') || extractChannelIdFromUrl();
    }

    function getChannelHandle() {
        const path = window.location.pathname;
        const matches = path.match(/\/@([^/]+)/);
        return matches ? matches[1] : null;
    }

    function extractChannelIdFromUrl() {
        const path = window.location.pathname;
        const matches = path.match(/\/(channel|user|c)\/([^/]+)/);
        return matches ? matches[2] : null;
    }

    // Utility Functions
    function addMaterialIconsStylesheet() {
        if (!document.querySelector('#material-icons-stylesheet')) {
            const link = document.createElement('link');
            link.id = 'material-icons-stylesheet';
            link.rel = 'stylesheet';
            link.href = 'https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@24,150,0,0&icon_names=cell_tower,lock_open';
            document.head.appendChild(link);
        }
    }

    function observePageChanges() {
        const observer = new MutationObserver((mutations) => {
            const targetElement = document.querySelector('yt-flexible-actions-view-model');
            if (targetElement && targetElement.querySelector('div.yt-flexible-actions-view-model-wiz__action')) {
                addStatsButtons();
                observer.disconnect();
            }
        });

        observer.observe(document.body, {
            childList: true,
            subtree: true
        });
    }

    // Initialization
    function init() {
        if (window.location.pathname.includes('/channel/') ||
            window.location.pathname.includes('/@') ||
            window.location.pathname.includes('/c/') ||
            window.location.pathname.includes('/user/')) {
            addMaterialIconsStylesheet();
            observePageChanges();
        }
    }

    // Run on initial page load
    init();

    // Listen for YouTube spa navigation events
    window.addEventListener('yt-navigate-finish', init);
})();
长期地址
遇到问题?请前往 GitHub 提 Issues。