您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Advanced YouTube video downloader with quality selection and progress tracking
// ==UserScript== // @name YouTube Advanced Downloader // @namespace http://tampermonkey.net/ // @version 1.1 // @description Advanced YouTube video downloader with quality selection and progress tracking // @author Anassk // @match https://www.youtube.com/* // @match https://www.youtube.com/watch?v=* // @grant GM_registerMenuCommand // @grant GM_xmlhttpRequest // @grant GM_getValue // @grant GM_setValue // @grant GM_notification // @connect * // @license MIT // ==/UserScript== (function() { 'use strict'; // Configuration with storage let API_KEY = GM_getValue('API_KEY', 'Your API Key'); let API_BASE = GM_getValue('API_BASE', 'Your API Base URL'); // Define qualities const QUALITIES = [ { label: 'Audio Only (M4A)', value: 'audio' }, { label: '144p', value: '144p' }, { label: '240p', value: '240p' }, { label: '360p', value: '360p' }, { label: '480p', value: '480p' }, { label: '720p', value: '720p' }, { label: '1080p', value: '1080p' }, { label: 'Highest Quality', value: 'highest' } ]; // Configuration UI function showConfig() { // Create dialog styles if not exists let style = document.getElementById('yt-dl-config-style'); if (!style) { style = document.createElement('style'); style.id = 'yt-dl-config-style'; style.textContent = ` .yt-dl-config-dialog { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: #1a1a1a; color: white; padding: 20px; border-radius: 8px; z-index: 10000; width: 400px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); border: 1px solid #333; } .yt-dl-config-dialog h2 { margin: 0 0 15px 0; font-size: 18px; color: #fff; } .yt-dl-config-dialog .input-group { margin-bottom: 15px; } .yt-dl-config-dialog label { display: block; margin-bottom: 5px; color: #aaa; } .yt-dl-config-dialog input { width: 100%; padding: 8px; background: #333; color: white; border: 1px solid #444; border-radius: 4px; margin-bottom: 10px; } .yt-dl-config-dialog .buttons { display: flex; justify-content: flex-end; gap: 10px; } .yt-dl-config-dialog button { padding: 8px 16px; border: none; border-radius: 4px; cursor: pointer; } .yt-dl-config-dialog .save-btn { background: #2196F3; color: white; } .yt-dl-config-dialog .cancel-btn { background: #666; color: white; } .yt-dl-config-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.7); z-index: 9999; } `; document.head.appendChild(style); } // Create dialog const overlay = document.createElement('div'); overlay.className = 'yt-dl-config-overlay'; const dialog = document.createElement('div'); dialog.className = 'yt-dl-config-dialog'; dialog.innerHTML = ` <h2>⚙️ Configure YouTube Downloader</h2> <div class="input-group"> <label for="api-key">API Key:</label> <input type="password" id="api-key" value="${API_KEY}" placeholder="Enter your API key"> <label for="api-base">API Base URL:</label> <input type="text" id="api-base" value="${API_BASE}" placeholder="Enter your API base URL"> </div> <div class="buttons"> <button class="cancel-btn">Cancel</button> <button class="save-btn">Save</button> </div> `; overlay.appendChild(dialog); document.body.appendChild(overlay); // Handle buttons dialog.querySelector('.save-btn').addEventListener('click', () => { const newApiKey = dialog.querySelector('#api-key').value.trim(); const newApiBase = dialog.querySelector('#api-base').value.trim(); if (newApiKey && newApiBase) { GM_setValue('API_KEY', newApiKey); GM_setValue('API_BASE', newApiBase); API_KEY = newApiKey; API_BASE = newApiBase; showNotification('Configuration saved successfully! ✅'); document.body.removeChild(overlay); } else { showNotification('Please fill in all fields! ⚠️'); } }); dialog.querySelector('.cancel-btn').addEventListener('click', () => { document.body.removeChild(overlay); }); } // Notification helper function showNotification(text, timeout = 3000) { GM_notification({ text: text, title: 'YouTube Downloader', timeout: timeout }); } // Configuration check function checkConfig() { if (API_KEY === 'Your API Key' || API_BASE === 'Your API Base URL') { showNotification('Please configure the downloader first! Click the Tampermonkey icon and select "⚙️ Configure"'); setTimeout(showConfig, 1000); return false; } return true; } // Create and show download dialog function showDialog() { if (!checkConfig()) return; // Create dialog styles const style = document.createElement('style'); style.textContent = ` .yt-dl-dialog { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: #1a1a1a; color: white; padding: 20px; border-radius: 8px; z-index: 10000; min-width: 300px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); border: 1px solid #333; } .yt-dl-dialog h2 { margin: 0 0 15px 0; font-size: 18px; display: flex; align-items: center; gap: 8px; } .yt-dl-dialog select { width: 100%; padding: 8px; margin-bottom: 15px; background: #333; color: white; border: 1px solid #444; border-radius: 4px; } .yt-dl-dialog .buttons { display: flex; justify-content: space-between; gap: 10px; } .yt-dl-dialog button { padding: 8px 16px; border: none; border-radius: 4px; cursor: pointer; flex: 1; display: flex; align-items: center; justify-content: center; gap: 5px; } .yt-dl-dialog .download-btn { background: #2196F3; color: white; } .yt-dl-dialog .link-btn { background: #4CAF50; color: white; } .yt-dl-dialog .cancel-btn { background: #666; color: white; } .yt-dl-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.7); z-index: 9999; } #download-progress { position: fixed; bottom: 20px; right: 20px; background: #1a1a1a; color: white; padding: 15px; border-radius: 8px; z-index: 10001; min-width: 300px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); display: none; } #download-progress .progress-bar { height: 5px; background: #333; border-radius: 3px; margin: 10px 0; overflow: hidden; } #download-progress .progress-bar-fill { height: 100%; background: #2196F3; width: 0%; transition: width 0.3s ease; } `; document.head.appendChild(style); // Create dialog const overlay = document.createElement('div'); overlay.className = 'yt-dl-overlay'; const dialog = document.createElement('div'); dialog.className = 'yt-dl-dialog'; dialog.innerHTML = ` <h2>📥 Download Video</h2> <select id="quality-select"> ${QUALITIES.map(q => `<option value="${q.value}">${q.label}</option>`).join('')} </select> <div class="buttons"> <button class="download-btn">💾 Download</button> <button class="link-btn">🔗 Get Link</button> <button class="cancel-btn">❌ Cancel</button> </div> `; overlay.appendChild(dialog); document.body.appendChild(overlay); // Handle buttons dialog.querySelector('.download-btn').addEventListener('click', () => { const quality = dialog.querySelector('#quality-select').value; document.body.removeChild(overlay); streamDownload(quality); }); dialog.querySelector('.link-btn').addEventListener('click', () => { const quality = dialog.querySelector('#quality-select').value; document.body.removeChild(overlay); quickLink(quality); }); dialog.querySelector('.cancel-btn').addEventListener('click', () => { document.body.removeChild(overlay); }); } // Progress UI functions function showProgress(text, progress = null) { let container = document.getElementById('download-progress'); if (!container) { container = document.createElement('div'); container.id = 'download-progress'; container.innerHTML = ` <div class="status"></div> <div class="progress-bar"> <div class="progress-bar-fill"></div> </div> `; document.body.appendChild(container); } container.style.display = 'block'; container.querySelector('.status').textContent = text; if (progress !== null) { container.querySelector('.progress-bar-fill').style.width = `${progress}%`; } } function hideProgress() { const container = document.getElementById('download-progress'); if (container) { container.style.display = 'none'; } } // Download functions async function streamDownload(quality) { if (!checkConfig()) return; const videoId = new URLSearchParams(window.location.search).get('v'); if (!videoId) { showNotification('No video found! ❌'); return; } try { showProgress('Starting download...', 0); const url = `${API_BASE}/api/stream/${videoId}?quality=${quality}`; const title = document.title.replace(' - YouTube', '').trim(); const ext = quality === 'audio' ? 'm4a' : 'mp4'; const response = await new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'GET', url, responseType: 'blob', headers: { 'X-API-Key': API_KEY }, onprogress: (progress) => { if (progress.lengthComputable) { const percent = (progress.loaded / progress.total * 100).toFixed(1); showProgress(`Downloading: ${percent}%`, percent); } }, onload: resolve, onerror: reject }); }); const blob = new Blob([response.response]); const downloadUrl = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = downloadUrl; a.download = `${title}.${ext}`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(downloadUrl); showProgress('Download complete! ✅', 100); setTimeout(hideProgress, 3000); } catch (error) { console.error('Download error:', error); showProgress('Download failed! ❌'); showNotification('Download failed! Check console for details.'); setTimeout(hideProgress, 3000); } } async function quickLink(quality) { if (!checkConfig()) return; const videoId = new URLSearchParams(window.location.search).get('v'); if (!videoId) { showNotification('No video found! ❌'); return; } try { showProgress('Getting download link...'); const response = await new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'GET', url: `${API_BASE}/api/download/${videoId}?quality=${quality}`, headers: { 'X-API-Key': API_KEY }, onload: (response) => { if (response.status === 200) { resolve(JSON.parse(response.responseText)); } else { reject(new Error(response.statusText)); } }, onerror: reject }); }); window.open(response.download_url, '_blank'); showProgress('Link opened in new tab! ✅'); setTimeout(hideProgress, 2000); } catch (error) { console.error('Error:', error); showProgress('Failed to get link! ❌'); showNotification('Failed to get download link! Check console for details.'); setTimeout(hideProgress, 3000); } } // Add context menu button to video thumbnails function addContextMenuToThumbnails() { const thumbnails = document.querySelectorAll('a#thumbnail'); thumbnails.forEach(thumbnail => { if (!thumbnail.dataset.dlEnabled) { thumbnail.addEventListener('contextmenu', (e) => { const videoId = thumbnail.href?.match(/[?&]v=([^&]+)/)?.[1]; if (videoId) { e.preventDefault(); const rect = thumbnail.getBoundingClientRect(); showContextMenu(videoId, rect.left, rect.top); } }); thumbnail.dataset.dlEnabled = 'true'; } }); } // Context menu for thumbnails function showContextMenu(videoId, x, y) { const menu = document.createElement('div'); menu.className = 'yt-dl-context-menu'; menu.style.cssText = ` position: fixed; left: ${x}px; top: ${y}px; background: #1a1a1a; border: 1px solid #333; border-radius: 4px; padding: 5px 0; z-index: 10000; box-shadow: 0 2px 10px rgba(0,0,0,0.2); `; menu.innerHTML = ` <div style="padding: 8px 12px; color: #fff; font-size: 14px; cursor: pointer; hover: background-color: #333;"> 📥 Download Video </div> `; document.body.appendChild(menu); // Handle click menu.addEventListener('click', () => { window.location.href = `https://www.youtube.com/watch?v=${videoId}`; setTimeout(showDialog, 1000); }); // Remove menu on click outside function removeMenu(e) { if (!menu.contains(e.target)) { document.body.removeChild(menu); document.removeEventListener('click', removeMenu); } } setTimeout(() => document.addEventListener('click', removeMenu), 0); } // YouTube spa navigation handler function handleSpaNavigation() { const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { if (mutation.type === 'childList') { addContextMenuToThumbnails(); } }); }); observer.observe(document.body, { childList: true, subtree: true }); } // Initialize function init() { // Add initial context menus addContextMenuToThumbnails(); // Handle SPA navigation handleSpaNavigation(); // Add configuration command GM_registerMenuCommand('⚙️ Configure', showConfig); // Add download command GM_registerMenuCommand('📥 Download Video', showDialog); } // Start the script init(); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址