Enhanced YouTube Download Button

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

目前為 2025-11-30 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 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;

})();