- // ==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();
- })();