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