YouTube Video Downloader with Quality Options

Adds a download button next to like/share buttons with quality options (144p, 360p, 480p, 1080p)

Size

8.4 KB

Version

1.0.1

Created

Mar 10, 2026

Updated

about 1 month ago

1// ==UserScript==
2// @name		YouTube Video Downloader with Quality Options
3// @description		Adds a download button next to like/share buttons with quality options (144p, 360p, 480p, 1080p)
4// @version		1.0.1
5// @match		https://*.m.youtube.com/*
6// @match		https://m.youtube.com/*
7// ==/UserScript==
8(function() {
9    'use strict';
10
11    console.log('YouTube Video Downloader extension loaded');
12
13    // Function to extract video ID from URL
14    function getVideoId() {
15        const urlParams = new URLSearchParams(window.location.search);
16        return urlParams.get('v');
17    }
18
19    // Function to create download button
20    function createDownloadButton() {
21        console.log('Creating download button...');
22        
23        // Check if button already exists
24        if (document.querySelector('#yt-download-btn')) {
25            console.log('Download button already exists');
26            return;
27        }
28
29        // Find the action bar container
30        const actionBar = document.querySelector('ytm-slim-video-action-bar-renderer .slim-video-action-bar-actions');
31        
32        if (!actionBar) {
33            console.log('Action bar not found, will retry...');
34            return;
35        }
36
37        console.log('Action bar found, creating button');
38
39        // Create the download button container
40        const downloadBtnContainer = document.createElement('button-view-model');
41        downloadBtnContainer.className = 'ytSpecButtonViewModelHost';
42        downloadBtnContainer.id = 'yt-download-btn';
43
44        // Create the button
45        const downloadBtn = document.createElement('button');
46        downloadBtn.className = 'yt-spec-button-shape-next yt-spec-button-shape-next--tonal yt-spec-button-shape-next--mono yt-spec-button-shape-next--size-m yt-spec-button-shape-next--icon-leading';
47        downloadBtn.setAttribute('aria-label', 'Download video');
48        downloadBtn.setAttribute('title', 'Download');
49
50        // Create icon container
51        const iconDiv = document.createElement('div');
52        iconDiv.className = 'yt-spec-button-shape-next__icon';
53        iconDiv.setAttribute('aria-hidden', 'true');
54        iconDiv.innerHTML = `
55            <svg viewBox="0 0 24 24" style="width: 24px; height: 24px; fill: currentColor;">
56                <path d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z"/>
57            </svg>
58        `;
59
60        // Create button text
61        const textDiv = document.createElement('div');
62        textDiv.className = 'yt-spec-button-shape-next__button-text-content';
63        textDiv.textContent = 'Download';
64
65        // Create touch feedback
66        const touchFeedback = document.createElement('yt-touch-feedback-shape');
67        touchFeedback.className = 'yt-spec-touch-feedback-shape yt-spec-touch-feedback-shape--touch-response';
68        touchFeedback.setAttribute('aria-hidden', 'true');
69
70        // Assemble button
71        downloadBtn.appendChild(iconDiv);
72        downloadBtn.appendChild(textDiv);
73        downloadBtn.appendChild(touchFeedback);
74        downloadBtnContainer.appendChild(downloadBtn);
75
76        // Create quality options menu (hidden by default)
77        const qualityMenu = document.createElement('div');
78        qualityMenu.id = 'yt-quality-menu';
79        qualityMenu.style.cssText = `
80            display: none;
81            position: fixed;
82            bottom: 0;
83            left: 0;
84            right: 0;
85            background: #282828;
86            border-radius: 12px 12px 0 0;
87            padding: 16px;
88            z-index: 9999;
89            box-shadow: 0 -2px 10px rgba(0,0,0,0.3);
90        `;
91
92        const menuTitle = document.createElement('div');
93        menuTitle.style.cssText = `
94            color: #fff;
95            font-size: 16px;
96            font-weight: 500;
97            margin-bottom: 16px;
98            text-align: center;
99        `;
100        menuTitle.textContent = 'Select Quality';
101
102        const qualities = ['144p', '360p', '480p', '1080p'];
103        const qualityButtons = document.createElement('div');
104        qualityButtons.style.cssText = 'display: flex; flex-direction: column; gap: 8px;';
105
106        qualities.forEach(quality => {
107            const qualityBtn = document.createElement('button');
108            qualityBtn.textContent = quality;
109            qualityBtn.style.cssText = `
110                background: #3ea6ff;
111                color: #fff;
112                border: none;
113                border-radius: 8px;
114                padding: 12px;
115                font-size: 14px;
116                font-weight: 500;
117                cursor: pointer;
118                transition: background 0.2s;
119            `;
120            
121            qualityBtn.addEventListener('click', () => {
122                handleDownload(quality);
123                qualityMenu.style.display = 'none';
124            });
125
126            qualityBtn.addEventListener('touchstart', function() {
127                this.style.background = '#2a8fd8';
128            });
129
130            qualityBtn.addEventListener('touchend', function() {
131                this.style.background = '#3ea6ff';
132            });
133
134            qualityButtons.appendChild(qualityBtn);
135        });
136
137        const cancelBtn = document.createElement('button');
138        cancelBtn.textContent = 'Cancel';
139        cancelBtn.style.cssText = `
140            background: #606060;
141            color: #fff;
142            border: none;
143            border-radius: 8px;
144            padding: 12px;
145            font-size: 14px;
146            font-weight: 500;
147            cursor: pointer;
148            margin-top: 8px;
149        `;
150        cancelBtn.addEventListener('click', () => {
151            qualityMenu.style.display = 'none';
152        });
153
154        qualityMenu.appendChild(menuTitle);
155        qualityMenu.appendChild(qualityButtons);
156        qualityMenu.appendChild(cancelBtn);
157        document.body.appendChild(qualityMenu);
158
159        // Add click event to show quality menu
160        downloadBtn.addEventListener('click', (e) => {
161            e.preventDefault();
162            e.stopPropagation();
163            console.log('Download button clicked');
164            qualityMenu.style.display = 'block';
165        });
166
167        // Insert the button after the share button
168        actionBar.appendChild(downloadBtnContainer);
169        console.log('Download button added successfully');
170    }
171
172    // Function to handle download
173    function handleDownload(quality) {
174        const videoId = getVideoId();
175        if (!videoId) {
176            console.error('Video ID not found');
177            alert('Could not find video ID');
178            return;
179        }
180
181        console.log(`Downloading video ${videoId} in ${quality}`);
182        
183        // Using a popular YouTube download service
184        const downloadUrl = `https://www.y2mate.com/youtube/${videoId}`;
185        
186        // Open in new tab
187        window.open(downloadUrl, '_blank');
188        
189        // Show feedback
190        showDownloadFeedback(quality);
191    }
192
193    // Function to show download feedback
194    function showDownloadFeedback(quality) {
195        const feedback = document.createElement('div');
196        feedback.style.cssText = `
197            position: fixed;
198            top: 20px;
199            left: 50%;
200            transform: translateX(-50%);
201            background: #3ea6ff;
202            color: #fff;
203            padding: 12px 24px;
204            border-radius: 8px;
205            z-index: 10000;
206            font-size: 14px;
207            box-shadow: 0 2px 8px rgba(0,0,0,0.3);
208        `;
209        feedback.textContent = `Opening download page for ${quality}...`;
210        document.body.appendChild(feedback);
211
212        setTimeout(() => {
213            feedback.remove();
214        }, 3000);
215    }
216
217    // Function to initialize the extension
218    function init() {
219        console.log('Initializing YouTube Video Downloader...');
220        
221        // Wait for the page to load
222        if (document.readyState === 'loading') {
223            document.addEventListener('DOMContentLoaded', () => {
224                setTimeout(createDownloadButton, 2000);
225            });
226        } else {
227            setTimeout(createDownloadButton, 2000);
228        }
229
230        // Watch for navigation changes (YouTube is a SPA)
231        let lastUrl = location.href;
232        new MutationObserver(() => {
233            const currentUrl = location.href;
234            if (currentUrl !== lastUrl) {
235                lastUrl = currentUrl;
236                console.log('URL changed, recreating download button');
237                setTimeout(createDownloadButton, 2000);
238            }
239        }).observe(document.body, { childList: true, subtree: true });
240    }
241
242    // Start the extension
243    init();
244})();