您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds a customizable download button to YouTube video pages.
// ==UserScript== // @name YouTube Download Button // @namespace http://tampermonkey.net/ // @version 4.3 // @description Adds a customizable download button to YouTube video pages. // @author jnbn05 with help from Gemini, ChatGPT & Grok // @match https://www.youtube.com/watch?v=* // @grant none // @license GPL-3.0-or-later // ==/UserScript== /* YouTube Download Button userscript Copyright (C) 2025 jnbn05 This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. */ (function() { 'use strict'; // *** USER CONFIGURATION *** // Change these values to use a different third-party video downloader. // Choose the URL format type: // 'query_parameter': Appends the full YouTube URL as a query parameter (e.g., https://site.com/?url=... ) // 'id_only': Appends only the video ID as a parameter (e.g., https://site.com/?id=...) // 'prefix_to_host': Adds a prefix to the YouTube URL's hostname (e.g., ssyoutube.com/... ) // 'suffix_to_domain': Adds a suffix to the domain name (e.g., youtubepi.com/... ) const DOWNLOAD_URL_TYPE = 'id_only'; // The template for downloaders that use the 'query_parameter' method. // The placeholder `[URL]` will be replaced with the current YouTube video URL. const QUERY_PARAMETER_URL_TEMPLATE = `https://cobalt.tools/?url=[URL]`; // The template for downloaders that use the 'id_only' method. // The placeholder `[ID]` will be replaced with the YouTube video ID. const ID_ONLY_URL_TEMPLATE = `https://yt1z.net/en-Izl/?id=[ID]`; // If DOWNLOAD_URL_TYPE is 'prefix_to_host', use this prefix. const HOST_PREFIX = 'ss'; // If DOWNLOAD_URL_TYPE is 'suffix_to_domain', use this suffix. const DOMAIN_SUFFIX = 'pi'; // ** END OF CONFIGURATION ** function getVideoId() { const urlParams = new URLSearchParams(window.location.search); return urlParams.get('v'); } function createDownloadButton() { const videoId = getVideoId(); if (!videoId) { return null; } const button = document.createElement('button'); button.classList.add('download-button'); button.style.display = 'flex'; button.style.alignItems = 'center'; button.style.padding = '6px 10px'; button.style.border = 'none'; button.style.fontFamily = '"Roboto", sans-serif'; button.style.fontWeight = '450'; button.style.borderRadius = '20px'; button.style.cursor = 'pointer'; button.style.fontSize = '14px'; button.style.marginLeft = '8px'; button.style.gap = '6px'; button.style.transition = 'background-color 0.2s'; const svgIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); svgIcon.setAttribute('viewBox', '0 0 24 24'); svgIcon.setAttribute('fill', 'currentColor'); svgIcon.setAttribute('width', '23'); svgIcon.setAttribute('height', '23'); svgIcon.innerHTML = `<path d="M12.5535 16.5061C12.4114 16.6615 12.2106 16.75 12 16.75C11.7894 16.75 11.5886 16.6615 11.4465 16.5061L7.44648 12.1311C7.16698 11.8254 7.18822 11.351 7.49392 11.0715C7.79963 10.792 8.27402 10.8132 8.55352 11.1189L11.25 14.0682V3C11.25 2.58579 11.5858 2.25 12 2.25C12.4142 2.25 12.75 2.58579 12.75 3V14.0682L15.4465 11.1189C15.726 10.8132 16.2004 10.792 16.5061 11.0715C16.8118 11.351 16.833 11.8254 16.5535 12.1311L12.5535 16.5061Z" fill="currentColor"></path><path d="M3.75 15C3.75 14.5858 3.41422 14.25 3 14.25C2.58579 14.25 2.25 14.5858 2.25 15V15.0549C2.24998 16.4225 2.24996 17.5248 2.36652 18.3918C2.48754 19.2919 2.74643 20.0497 3.34835 20.6516C3.95027 21.2536 4.70814 21.5125 5.60825 21.6335C6.47522 21.75 7.57754 21.75 8.94513 21.75H15.0549C16.4225 21.75 17.5248 21.75 18.3918 21.6335C19.2919 21.5125 20.0497 21.2536 20.6517 20.6516C21.2536 20.0497 21.5125 19.2919 21.6335 18.3918C21.75 17.5248 21.75 16.4225 21.75 15.0549V15C21.75 14.5858 21.4142 14.25 21 14.25C20.5858 14.25 20.25 14.5858 20.25 15C20.25 16.4354 20.2484 17.4365 20.1469 18.1919C20.0482 18.9257 19.8678 19.3142 19.591 19.591C19.3142 19.8678 18.9257 20.0482 18.1919 20.1469C17.4365 20.2484 16.4354 20.25 15 20.25H9C7.56459 20.25 6.56347 20.2484 5.80812 20.1469C5.07435 20.0482 4.68577 19.8678 4.40901 19.591C4.13225 19.3142 3.9518 18.9257 3.85315 18.1919C3.75159 17.4365 3.75 16.4354 3.75 15Z"/>`; button.appendChild(svgIcon); button.appendChild(document.createTextNode('Download')); button.addEventListener('mouseenter', () => { button.style.backgroundColor = getComputedStyle(document.documentElement).getPropertyValue('--yt-spec-button-chip-background-hover').trim(); }); button.addEventListener('mouseleave', () => { updateButtonTheme(); }); function updateButtonTheme() { // Updated to be more resilient to YouTube theme changes const computedStyle = getComputedStyle(document.documentElement); const bgColor = computedStyle.getPropertyValue('--efyt-control-bar-background-color').trim(); const textColor = computedStyle.getPropertyValue('--yt-spec-text-primary').trim(); button.style.backgroundColor = bgColor; button.style.color = textColor; } updateButtonTheme(); const themeObserver = new MutationObserver(updateButtonTheme); themeObserver.observe(document.documentElement, { attributes: true, attributeFilter: ['dark'] }); button.addEventListener('click', () => { let downloadUrl; const videoId = getVideoId(); if (!videoId) { console.error("Could not find video ID."); return; } if (DOWNLOAD_URL_TYPE === 'query_parameter') { const youtubeUrl = `https://www.youtube.com/watch?v=${videoId}`; downloadUrl = QUERY_PARAMETER_URL_TEMPLATE.replace('[URL]', encodeURIComponent(youtubeUrl)); } else if (DOWNLOAD_URL_TYPE === 'id_only') { downloadUrl = ID_ONLY_URL_TEMPLATE.replace('[ID]', videoId); } else if (DOWNLOAD_URL_TYPE === 'prefix_to_host') { const urlObj = new URL(window.location.href); const hostWithPrefix = `${HOST_PREFIX}${urlObj.hostname}`; urlObj.hostname = hostWithPrefix; downloadUrl = urlObj.href; } else if (DOWNLOAD_URL_TYPE === 'suffix_to_domain') { const urlObj = new URL(window.location.href); const hostParts = urlObj.hostname.split('.'); const youtubeIndex = hostParts.indexOf('youtube'); if (youtubeIndex !== -1) { hostParts[youtubeIndex] = `youtube${DOMAIN_SUFFIX}`; } urlObj.hostname = hostParts.join('.'); downloadUrl = urlObj.href; } else { console.error('Invalid DOWNLOAD_URL_TYPE specified. Using default.'); const youtubeUrl = `https://www.youtube.com/watch?v=${videoId}`; downloadUrl = QUERY_PARAMETER_URL_TEMPLATE.replace('[URL]', encodeURIComponent(youtubeUrl)); } window.open(downloadUrl, '_blank'); }); return button; } function addDownloadButtonToPage() { const videoId = getVideoId(); if (!videoId) { return; } const actionContainer = document.querySelector('ytd-watch-metadata #top-level-buttons-computed'); const customButton = document.querySelector('.download-button'); const nativeDownloadButton = document.querySelector('ytd-download-button-renderer'); // Remove the native button if it exists if (nativeDownloadButton) { nativeDownloadButton.remove(); } // Add our custom button only if the container exists and our button isn't already there if (actionContainer && !customButton) { const downloadButton = createDownloadButton(); if (downloadButton) { actionContainer.appendChild(downloadButton); console.log('Download button added for video:', videoId); } } } function init() { // One-time disclaimer pop-up check if (localStorage.getItem('disclaimer_shown') !== 'true') { const disclaimerText = "Disclaimer: This script provides links to third-party video download services. You are responsible for how you use these services, and the use of such services is at your own discretion and risk. By clicking 'OK', you acknowledge and agree to these terms."; alert(disclaimerText); localStorage.setItem('disclaimer_shown', 'true'); } // Use a single MutationObserver to handle all relevant page changes const observer = new MutationObserver(addDownloadButtonToPage); // Start observing a stable parent element, like the body observer.observe(document.body, { childList: true, subtree: true }); // Initial run addDownloadButtonToPage(); } init(); })();