Download Instagram videos, images, and reels individually or batch download entire profiles in one click
Size
12.1 KB
Version
1.0.1
Created
Mar 7, 2026
Updated
14 days ago
1// ==UserScript==
2// @name Instagram Media Downloader
3// @description Download Instagram videos, images, and reels individually or batch download entire profiles in one click
4// @version 1.0.1
5// @match https://www.instagram.com/*
6// @match https://instagram.com/*
7// @icon https://robomonkey.io/favicon.ico
8// @grant GM.xmlhttpRequest
9// @grant GM.getValue
10// @grant GM.setValue
11// @connect cdninstagram.com
12// @connect instagram.com
13// @connect fbcdn.net
14// ==/UserScript==
15(function() {
16 'use strict';
17
18 console.log('Instagram Media Downloader initialized');
19
20 // Utility function to debounce
21 function debounce(func, wait) {
22 let timeout;
23 return function executedFunction(...args) {
24 const later = () => {
25 clearTimeout(timeout);
26 func(...args);
27 };
28 clearTimeout(timeout);
29 timeout = setTimeout(later, wait);
30 };
31 }
32
33 // Download a file
34 async function downloadFile(url, filename) {
35 try {
36 console.log('Downloading:', filename, 'from:', url);
37 const response = await GM.xmlhttpRequest({
38 method: 'GET',
39 url: url,
40 responseType: 'blob',
41 headers: {
42 'User-Agent': navigator.userAgent
43 }
44 });
45
46 const blob = response.response;
47 const blobUrl = URL.createObjectURL(blob);
48 const a = document.createElement('a');
49 a.href = blobUrl;
50 a.download = filename;
51 document.body.appendChild(a);
52 a.click();
53 document.body.removeChild(a);
54 URL.revokeObjectURL(blobUrl);
55 console.log('Downloaded successfully:', filename);
56 return true;
57 } catch (error) {
58 console.error('Download failed:', error);
59 return false;
60 }
61 }
62
63 // Extract media URL from Instagram post
64 function extractMediaUrl(article) {
65 try {
66 // Try to find video first
67 const video = article.querySelector('video[src]');
68 if (video && video.src) {
69 return { url: video.src, type: 'video' };
70 }
71
72 // Try to find image
73 const img = article.querySelector('img[src*="scontent"]');
74 if (img && img.src) {
75 return { url: img.src, type: 'image' };
76 }
77
78 return null;
79 } catch (error) {
80 console.error('Error extracting media URL:', error);
81 return null;
82 }
83 }
84
85 // Create download button for individual posts
86 function createDownloadButton() {
87 const button = document.createElement('button');
88 button.innerHTML = `
89 <svg aria-label="Download" fill="currentColor" height="24" viewBox="0 0 24 24" width="24">
90 <path d="M12 16L7 11h3V3h4v8h3l-5 5zm9-4v9H3v-9h2v7h14v-7h2z"></path>
91 </svg>
92 `;
93 button.style.cssText = `
94 background: transparent;
95 border: none;
96 cursor: pointer;
97 padding: 8px;
98 display: flex;
99 align-items: center;
100 justify-content: center;
101 color: rgb(245, 245, 245);
102 transition: opacity 0.2s;
103 `;
104 button.onmouseover = () => button.style.opacity = '0.5';
105 button.onmouseout = () => button.style.opacity = '1';
106 return button;
107 }
108
109 // Add download button to posts
110 function addDownloadButtonToPost(article) {
111 // Check if button already exists
112 if (article.querySelector('.ig-download-btn')) {
113 return;
114 }
115
116 // Find the action bar (like, comment, share buttons)
117 const actionBar = article.querySelector('section[class*="x1qjc9v5"]');
118 if (!actionBar) {
119 return;
120 }
121
122 const button = createDownloadButton();
123 button.className = 'ig-download-btn';
124
125 button.addEventListener('click', async (e) => {
126 e.preventDefault();
127 e.stopPropagation();
128
129 const media = extractMediaUrl(article);
130 if (media) {
131 const timestamp = Date.now();
132 const filename = `instagram_${media.type}_${timestamp}.${media.type === 'video' ? 'mp4' : 'jpg'}`;
133 button.innerHTML = '⏳';
134 const success = await downloadFile(media.url, filename);
135 if (success) {
136 button.innerHTML = '✓';
137 setTimeout(() => {
138 button.innerHTML = `
139 <svg aria-label="Download" fill="currentColor" height="24" viewBox="0 0 24 24" width="24">
140 <path d="M12 16L7 11h3V3h4v8h3l-5 5zm9-4v9H3v-9h2v7h14v-7h2z"></path>
141 </svg>
142 `;
143 }, 2000);
144 } else {
145 button.innerHTML = '✗';
146 setTimeout(() => {
147 button.innerHTML = `
148 <svg aria-label="Download" fill="currentColor" height="24" viewBox="0 0 24 24" width="24">
149 <path d="M12 16L7 11h3V3h4v8h3l-5 5zm9-4v9H3v-9h2v7h14v-7h2z"></path>
150 </svg>
151 `;
152 }, 2000);
153 }
154 } else {
155 alert('Could not find media to download');
156 }
157 });
158
159 // Insert button next to other action buttons
160 const firstButton = actionBar.querySelector('div[role="button"]');
161 if (firstButton && firstButton.parentElement) {
162 firstButton.parentElement.insertBefore(button, firstButton.parentElement.firstChild);
163 }
164 }
165
166 // Create batch download button for profile
167 function createBatchDownloadButton() {
168 const button = document.createElement('button');
169 button.id = 'ig-batch-download-btn';
170 button.textContent = 'Download All Media';
171 button.style.cssText = `
172 position: fixed;
173 bottom: 20px;
174 right: 20px;
175 background: linear-gradient(45deg, #f09433 0%, #e6683c 25%, #dc2743 50%, #cc2366 75%, #bc1888 100%);
176 color: white;
177 border: none;
178 padding: 12px 24px;
179 border-radius: 8px;
180 font-weight: 600;
181 font-size: 14px;
182 cursor: pointer;
183 z-index: 9999;
184 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
185 transition: transform 0.2s, box-shadow 0.2s;
186 display: none;
187 `;
188 button.onmouseover = () => {
189 button.style.transform = 'translateY(-2px)';
190 button.style.boxShadow = '0 6px 16px rgba(0, 0, 0, 0.4)';
191 };
192 button.onmouseout = () => {
193 button.style.transform = 'translateY(0)';
194 button.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.3)';
195 };
196 return button;
197 }
198
199 // Check if we're on a profile page
200 function isProfilePage() {
201 const path = window.location.pathname;
202 return path.match(/^\/[^\/]+\/?$/) && !path.match(/^\/(explore|direct|accounts|stories)/);
203 }
204
205 // Batch download all media from profile
206 async function batchDownloadProfile() {
207 const button = document.getElementById('ig-batch-download-btn');
208 const originalText = button.textContent;
209
210 try {
211 button.textContent = 'Collecting media...';
212 button.disabled = true;
213
214 // Scroll to load more posts
215 let lastHeight = document.body.scrollHeight;
216 let scrollAttempts = 0;
217 const maxScrollAttempts = 10;
218
219 while (scrollAttempts < maxScrollAttempts) {
220 window.scrollTo(0, document.body.scrollHeight);
221 await new Promise(resolve => setTimeout(resolve, 2000));
222
223 const newHeight = document.body.scrollHeight;
224 if (newHeight === lastHeight) {
225 break;
226 }
227 lastHeight = newHeight;
228 scrollAttempts++;
229 button.textContent = `Loading posts... (${scrollAttempts}/${maxScrollAttempts})`;
230 }
231
232 // Collect all media
233 const articles = document.querySelectorAll('article[role="presentation"]');
234 const mediaList = [];
235
236 articles.forEach(article => {
237 const media = extractMediaUrl(article);
238 if (media) {
239 mediaList.push(media);
240 }
241 });
242
243 if (mediaList.length === 0) {
244 alert('No media found to download');
245 button.textContent = originalText;
246 button.disabled = false;
247 return;
248 }
249
250 // Download all media
251 button.textContent = `Downloading 0/${mediaList.length}...`;
252 let downloaded = 0;
253
254 for (let i = 0; i < mediaList.length; i++) {
255 const media = mediaList[i];
256 const filename = `instagram_batch_${i + 1}_${media.type}_${Date.now()}.${media.type === 'video' ? 'mp4' : 'jpg'}`;
257 await downloadFile(media.url, filename);
258 downloaded++;
259 button.textContent = `Downloading ${downloaded}/${mediaList.length}...`;
260 // Small delay between downloads
261 await new Promise(resolve => setTimeout(resolve, 500));
262 }
263
264 button.textContent = `✓ Downloaded ${downloaded} files`;
265 setTimeout(() => {
266 button.textContent = originalText;
267 button.disabled = false;
268 }, 3000);
269
270 } catch (error) {
271 console.error('Batch download error:', error);
272 button.textContent = '✗ Download failed';
273 setTimeout(() => {
274 button.textContent = originalText;
275 button.disabled = false;
276 }, 3000);
277 }
278 }
279
280 // Initialize batch download button
281 function initBatchDownload() {
282 let batchButton = document.getElementById('ig-batch-download-btn');
283
284 if (!batchButton) {
285 batchButton = createBatchDownloadButton();
286 document.body.appendChild(batchButton);
287
288 batchButton.addEventListener('click', async (e) => {
289 e.preventDefault();
290 batchDownloadProfile();
291 });
292 }
293
294 // Show/hide based on page type
295 if (isProfilePage()) {
296 batchButton.style.display = 'block';
297 } else {
298 batchButton.style.display = 'none';
299 }
300 }
301
302 // Observe DOM changes and add download buttons
303 function observePosts() {
304 const observer = new MutationObserver(debounce(() => {
305 const articles = document.querySelectorAll('article[role="presentation"]');
306 articles.forEach(article => {
307 addDownloadButtonToPost(article);
308 });
309
310 // Update batch download button visibility
311 initBatchDownload();
312 }, 500));
313
314 observer.observe(document.body, {
315 childList: true,
316 subtree: true
317 });
318
319 // Initial run
320 const articles = document.querySelectorAll('article[role="presentation"]');
321 articles.forEach(article => {
322 addDownloadButtonToPost(article);
323 });
324
325 initBatchDownload();
326 }
327
328 // Initialize when page is ready
329 function init() {
330 if (document.readyState === 'loading') {
331 document.addEventListener('DOMContentLoaded', observePosts);
332 } else {
333 observePosts();
334 }
335
336 // Handle navigation changes (Instagram is a SPA)
337 let lastUrl = location.href;
338 new MutationObserver(() => {
339 const url = location.href;
340 if (url !== lastUrl) {
341 lastUrl = url;
342 setTimeout(() => {
343 observePosts();
344 }, 1000);
345 }
346 }).observe(document, { subtree: true, childList: true });
347 }
348
349 init();
350})();