Enhanced YouTube Download Button

Add download button to YouTube videos that redirects to addyoutube.com

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Enhanced YouTube Download Button
// @namespace    https://github.com/MXKXYZ
// @version      2.1
// @description  Add download button to YouTube videos that redirects to addyoutube.com
// @author       KXYZNEW ([email protected])
// @contact      [email protected]
// @homepage     https://github.com/MXKXYZ
// @supportURL   https://github.com/MXKXYZ
// @match        https://www.youtube.com/*
// @match        https://youtube.com/*
// @grant        none
// @run-at       document-start
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';
    
    // Script information
    const SCRIPT_INFO = {
        name: "Enhanced YouTube Download Button",
        version: "2.1",
        author: "KXYZNEW",
        email: "[email protected]",
        github: "MXKXYZ",
        created: "2024",
        description: "Adds download button to YouTube videos"
    };
    
    console.log(`[${SCRIPT_INFO.name} v${SCRIPT_INFO.version}] Initializing...`);
    console.log(`[${SCRIPT_INFO.name}] Author: ${SCRIPT_INFO.author} (${SCRIPT_INFO.email})`);
    
    const BASE_URL = "https://addyoutube.com";
    const BUTTON_ID = "enhancedDwnldBtn";
    
    // Multiple possible targets for better reliability
    const TARGET_SELECTORS = [
        "#owner",
        "#upload-info", 
        "#channel-name",
        ".ytd-video-owner-renderer",
        "#meta-contents",
        "#above-the-fold"
    ];

    const buttonStyle = `
        #${BUTTON_ID} {
            background-color: #28a745;
            color: #FFFFFF;
            border: 1px solid #3F3F3F;
            border-color: rgba(255,255,255,0.2);
            margin-left: 8px;
            padding: 0 16px;
            border-radius: 18px;
            font-size: 14px;
            font-family: Roboto, Noto, sans-serif;
            font-weight: 500;
            text-decoration: none;
            display: inline-flex;
            align-items: center;
            height: 36px;
            line-height: normal;
            cursor: pointer;
            transition: all 0.2s ease;
        }
        #${BUTTON_ID}:hover {
            background-color: #218838;
            transform: translateY(-1px);
            box-shadow: 0 2px 4px rgba(0,0,0,0.2);
        }
        #${BUTTON_ID}.loading {
            background-color: #6c757d;
            cursor: not-allowed;
        }
        #${BUTTON_ID}.error {
            background-color: #dc3545;
        }
        
        /* KXYZ Branding - Subtle watermark */
        .kxyz-watermark::after {
            content: "KXYZ";
            position: fixed;
            bottom: 10px;
            right: 10px;
            font-size: 10px;
            color: rgba(255,255,255,0.1);
            font-family: monospace;
            z-index: 9999;
            pointer-events: none;
        }
    `;

    // Add styles and branding
    const style = document.createElement('style');
    style.textContent = buttonStyle;
    document.head.appendChild(style);
    
    // Add subtle watermark
    document.body.classList.add('kxyz-watermark');

    function transformUrl(originalUrl) {
        return originalUrl.replace("youtube.com", "addyoutube.com");
    }

    function findBestTarget() {
        for (const selector of TARGET_SELECTORS) {
            const element = document.querySelector(selector);
            if (element && element.isConnected) {
                console.log(`[${SCRIPT_INFO.name}] Found target: ${selector}`);
                return element;
            }
        }
        console.warn(`[${SCRIPT_INFO.name}] No suitable target found`);
        return null;
    }

    function waitForElement() {
        return new Promise((resolve) => {
            // First check immediately
            const immediateElement = findBestTarget();
            if (immediateElement) {
                return resolve(immediateElement);
            }

            // Then wait for mutations
            const observer = new MutationObserver((mutations) => {
                const element = findBestTarget();
                if (element) {
                    resolve(element);
                    observer.disconnect();
                }
            });

            observer.observe(document.body, {
                childList: true,
                subtree: true,
                attributes: true,
                attributeFilter: ['class', 'id']
            });

            // Timeout after 10 seconds
            setTimeout(() => {
                observer.disconnect();
                resolve(findBestTarget());
            }, 10000);
        });
    }

    function createButton() {
        const downloadButton = document.createElement('a');
        downloadButton.href = transformUrl(window.location.href);
        downloadButton.target = '_blank';
        downloadButton.id = BUTTON_ID;
        downloadButton.innerText = 'Download';
        downloadButton.title = `Download this video via addyoutube.com | ${SCRIPT_INFO.name} v${SCRIPT_INFO.version} by ${SCRIPT_INFO.author}`;
        
        // Add KXYZ data attribute for identification
        downloadButton.setAttribute('data-kxyz-script', SCRIPT_INFO.name);
        downloadButton.setAttribute('data-author', SCRIPT_INFO.author);
        
        // Add loading state management
        downloadButton.addEventListener('click', function(e) {
            if (this.classList.contains('loading')) {
                e.preventDefault();
                return;
            }
            
            this.classList.add('loading');
            this.innerText = 'Redirecting...';
            
            // Log usage
            console.log(`[${SCRIPT_INFO.name}] Download initiated for: ${window.location.href}`);
            
            // Reset after 3 seconds if still on page
            setTimeout(() => {
                this.classList.remove('loading');
                this.innerText = 'Download';
            }, 3000);
        });
        
        return downloadButton;
    }

    function addButton() {
        // Remove existing button first
        const existingBtn = document.getElementById(BUTTON_ID);
        if (existingBtn) {
            existingBtn.remove();
        }

        waitForElement().then((btnContainer) => {
            if (!btnContainer) {
                console.warn(`[${SCRIPT_INFO.name}] Cannot find container for button`);
                return;
            }

            const downloadButton = createButton();
            
            // Try to insert after subscribe button if it exists
            const subscribeBtn = btnContainer.querySelector('#subscribe-button');
            if (subscribeBtn && subscribeBtn.parentNode) {
                subscribeBtn.parentNode.insertBefore(downloadButton, subscribeBtn.nextSibling);
            } else {
                btnContainer.appendChild(downloadButton);
            }
            
            console.log(`[${SCRIPT_INFO.name}] Button added successfully`);
            console.log(`[${SCRIPT_INFO.name}] Contact: ${SCRIPT_INFO.email} | GitHub: ${SCRIPT_INFO.github}`);
        }).catch(error => {
            console.error(`[${SCRIPT_INFO.name}] Error adding button: ${error}`);
        });
    }

    function updateButton() {
        const btn = document.getElementById(BUTTON_ID);
        if (btn) {
            btn.href = transformUrl(window.location.href);
            btn.classList.remove('loading', 'error');
            btn.innerText = 'Download';
        }
    }

    let currentUrl = window.location.href;

    function checkAndAddButton() {
        if (window.location.pathname === '/watch') {
            // Only re-add if URL changed or button doesn't exist
            if (window.location.href !== currentUrl || !document.getElementById(BUTTON_ID)) {
                addButton();
                currentUrl = window.location.href;
            }
        } else {
            // Remove button if not on watch page
            const btn = document.getElementById(BUTTON_ID);
            if (btn) {
                btn.remove();
            }
        }
    }

    // Enhanced navigation detection
    window.addEventListener("yt-navigate-finish", checkAndAddButton);
    
    // Also check on URL changes (for SPA navigation)
    let lastUrl = location.href;
    new MutationObserver(() => {
        const url = location.href;
        if (url !== lastUrl) {
            lastUrl = url;
            setTimeout(checkAndAddButton, 500);
        }
    }).observe(document, { subtree: true, childList: true });

    // Initial check
    setTimeout(checkAndAddButton, 1000);
    
    // Re-check every 5 seconds as backup
    setInterval(checkAndAddButton, 5000);
    
    // Export script info to global scope for debugging
    window.KXYZ_YT_DOWNLOAD_SCRIPT = SCRIPT_INFO;

})();