Download all high-quality images and videos from any Instagram profile
Size
21.8 KB
Version
1.1.1
Created
Mar 21, 2026
Updated
26 days ago
1// ==UserScript==
2// @name Instagram Media Downloader
3// @description Download all high-quality images and videos from any Instagram profile
4// @version 1.1.1
5// @match https://*.instagram.com/*
6// @icon https://static.cdninstagram.com/rsrc.php/y4/r/QaBlI0OZiks.ico
7// ==/UserScript==
8(function() {
9 'use strict';
10
11 console.log('Instagram Media Downloader extension loaded');
12
13 // Utility function to debounce
14 function debounce(func, wait) {
15 let timeout;
16 return function executedFunction(...args) {
17 const later = () => {
18 clearTimeout(timeout);
19 func(...args);
20 };
21 clearTimeout(timeout);
22 timeout = setTimeout(later, wait);
23 };
24 }
25
26 // Add custom styles for the download button and progress panel
27 TM_addStyle(`
28 .ig-download-btn {
29 background: linear-gradient(45deg, #f09433 0%, #e6683c 25%, #dc2743 50%, #cc2366 75%, #bc1888 100%);
30 color: white;
31 border: none;
32 padding: 8px 16px;
33 border-radius: 8px;
34 font-weight: 600;
35 font-size: 14px;
36 cursor: pointer;
37 margin-left: 8px;
38 transition: transform 0.2s, box-shadow 0.2s;
39 font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
40 }
41
42 .ig-download-btn:hover {
43 transform: translateY(-2px);
44 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
45 }
46
47 .ig-download-btn:disabled {
48 opacity: 0.6;
49 cursor: not-allowed;
50 transform: none;
51 }
52
53 .ig-download-panel {
54 position: fixed;
55 top: 80px;
56 right: 20px;
57 width: 350px;
58 max-height: 500px;
59 background: white;
60 border-radius: 12px;
61 box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
62 z-index: 9999;
63 overflow: hidden;
64 font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
65 }
66
67 .ig-download-panel-header {
68 background: linear-gradient(45deg, #f09433 0%, #e6683c 25%, #dc2743 50%, #cc2366 75%, #bc1888 100%);
69 color: white;
70 padding: 16px;
71 font-weight: 600;
72 font-size: 16px;
73 display: flex;
74 justify-content: space-between;
75 align-items: center;
76 }
77
78 .ig-download-panel-close {
79 background: rgba(255, 255, 255, 0.2);
80 border: none;
81 color: white;
82 width: 28px;
83 height: 28px;
84 border-radius: 50%;
85 cursor: pointer;
86 font-size: 18px;
87 line-height: 1;
88 transition: background 0.2s;
89 }
90
91 .ig-download-panel-close:hover {
92 background: rgba(255, 255, 255, 0.3);
93 }
94
95 .ig-download-panel-body {
96 padding: 16px;
97 max-height: 400px;
98 overflow-y: auto;
99 }
100
101 .ig-download-status {
102 margin-bottom: 16px;
103 padding: 12px;
104 background: #f0f0f0;
105 border-radius: 8px;
106 font-size: 14px;
107 color: #262626;
108 }
109
110 .ig-download-progress {
111 margin-top: 8px;
112 }
113
114 .ig-download-progress-bar {
115 width: 100%;
116 height: 8px;
117 background: #dbdbdb;
118 border-radius: 4px;
119 overflow: hidden;
120 }
121
122 .ig-download-progress-fill {
123 height: 100%;
124 background: linear-gradient(45deg, #f09433 0%, #e6683c 25%, #dc2743 50%, #cc2366 75%, #bc1888 100%);
125 transition: width 0.3s;
126 }
127
128 .ig-download-item {
129 display: flex;
130 align-items: center;
131 padding: 8px;
132 margin-bottom: 8px;
133 background: #fafafa;
134 border-radius: 6px;
135 font-size: 13px;
136 }
137
138 .ig-download-item-icon {
139 width: 40px;
140 height: 40px;
141 margin-right: 12px;
142 border-radius: 4px;
143 object-fit: cover;
144 }
145
146 .ig-download-item-info {
147 flex: 1;
148 color: #262626;
149 }
150
151 .ig-download-item-status {
152 font-size: 11px;
153 color: #8e8e8e;
154 margin-top: 4px;
155 }
156
157 .ig-download-item-success {
158 color: #00c853;
159 }
160
161 .ig-download-item-error {
162 color: #d32f2f;
163 }
164 `);
165
166 let downloadPanel = null;
167 let isDownloading = false;
168
169 // Create download panel
170 function createDownloadPanel() {
171 if (downloadPanel) {
172 downloadPanel.remove();
173 }
174
175 downloadPanel = document.createElement('div');
176 downloadPanel.className = 'ig-download-panel';
177 downloadPanel.innerHTML = `
178 <div class="ig-download-panel-header">
179 <span>Media Downloader</span>
180 <button class="ig-download-panel-close">×</button>
181 </div>
182 <div class="ig-download-panel-body">
183 <div class="ig-download-status">
184 <div id="ig-download-status-text">Initializing...</div>
185 <div class="ig-download-progress">
186 <div class="ig-download-progress-bar">
187 <div class="ig-download-progress-fill" id="ig-download-progress-fill" style="width: 0%"></div>
188 </div>
189 </div>
190 </div>
191 <div id="ig-download-items"></div>
192 </div>
193 `;
194
195 document.body.appendChild(downloadPanel);
196
197 // Close button handler
198 downloadPanel.querySelector('.ig-download-panel-close').addEventListener('click', () => {
199 downloadPanel.remove();
200 downloadPanel = null;
201 });
202
203 return downloadPanel;
204 }
205
206 // Update download status
207 function updateDownloadStatus(text, progress) {
208 if (!downloadPanel) return;
209
210 const statusText = downloadPanel.querySelector('#ig-download-status-text');
211 const progressFill = downloadPanel.querySelector('#ig-download-progress-fill');
212
213 if (statusText) statusText.textContent = text;
214 if (progressFill && progress !== undefined) {
215 progressFill.style.width = `${progress}%`;
216 }
217 }
218
219 // Add download item to panel
220 function addDownloadItem(thumbnail, filename, status) {
221 if (!downloadPanel) return;
222
223 const itemsContainer = downloadPanel.querySelector('#ig-download-items');
224 const item = document.createElement('div');
225 item.className = 'ig-download-item';
226 item.innerHTML = `
227 <img class="ig-download-item-icon" src="${thumbnail}" alt="">
228 <div class="ig-download-item-info">
229 <div>${filename}</div>
230 <div class="ig-download-item-status ${status === 'success' ? 'ig-download-item-success' : status === 'error' ? 'ig-download-item-error' : ''}">${status}</div>
231 </div>
232 `;
233 itemsContainer.appendChild(item);
234
235 // Auto-scroll to bottom
236 itemsContainer.scrollTop = itemsContainer.scrollHeight;
237 }
238
239 // Extract username from URL or page
240 function getUsername() {
241 const urlMatch = window.location.pathname.match(/^\/([^\/]+)/);
242 if (urlMatch && urlMatch[1] && !['explore', 'reels', 'direct', 'stories'].includes(urlMatch[1])) {
243 return urlMatch[1];
244 }
245 return null;
246 }
247
248 // Fetch user data from Instagram's internal API
249 async function fetchUserData(username) {
250 try {
251 console.log('Fetching user data for:', username);
252 const response = await fetch(`https://www.instagram.com/api/v1/users/web_profile_info/?username=${username}`, {
253 headers: {
254 'x-ig-app-id': '936619743392459',
255 'x-requested-with': 'XMLHttpRequest'
256 }
257 });
258
259 if (!response.ok) {
260 throw new Error('Failed to fetch user data');
261 }
262
263 const data = await response.json();
264 return data.data.user;
265 } catch (error) {
266 console.error('Error fetching user data:', error);
267 throw error;
268 }
269 }
270
271 // Fetch all posts from a user - Updated method
272 async function fetchAllPosts(userId) {
273 const posts = [];
274
275 try {
276 // Scroll and collect posts from the page
277 console.log('Attempting to collect posts by scrolling...');
278 updateDownloadStatus('Collecting posts from page...', 5);
279
280 // Get all post links and their images from the page
281 const postData = new Map();
282 let lastCount = 0;
283 let noChangeCount = 0;
284
285 // Scroll and collect posts
286 while (noChangeCount < 3) {
287 // Find all post links with their images
288 const postElements = document.querySelectorAll('a[href*="/p/"], a[href*="/reel/"]');
289 postElements.forEach(link => {
290 const href = link.getAttribute('href');
291 if (href && (href.includes('/p/') || href.includes('/reel/'))) {
292 const shortcodeMatch = href.match(/\/(p|reel)\/([^\/\?]+)/);
293 if (shortcodeMatch && shortcodeMatch[2]) {
294 const shortcode = shortcodeMatch[2];
295
296 // Try to find the image within this link
297 const img = link.querySelector('img');
298 if (img && img.src && !postData.has(shortcode)) {
299 postData.set(shortcode, {
300 shortcode: shortcode,
301 thumbnail: img.src,
302 type: href.includes('/reel/') ? 'video' : 'image',
303 url: href
304 });
305 }
306 }
307 }
308 });
309
310 const currentCount = postData.size;
311 console.log(`Found ${currentCount} unique posts`);
312 updateDownloadStatus(`Found ${currentCount} posts...`, 10);
313
314 if (currentCount === lastCount) {
315 noChangeCount++;
316 } else {
317 noChangeCount = 0;
318 lastCount = currentCount;
319 }
320
321 // Scroll down
322 window.scrollTo(0, document.body.scrollHeight);
323 await new Promise(resolve => setTimeout(resolve, 2000));
324 }
325
326 console.log(`Total unique posts found: ${postData.size}`);
327
328 // Convert to posts array format
329 postData.forEach((data, shortcode) => {
330 posts.push({
331 node: {
332 shortcode: shortcode,
333 thumbnail_src: data.thumbnail,
334 __typename: data.type === 'video' ? 'GraphVideo' : 'GraphImage',
335 id: shortcode,
336 postUrl: `https://www.instagram.com${data.url}`
337 }
338 });
339 });
340
341 } catch (error) {
342 console.error('Error fetching posts:', error);
343 }
344
345 return posts;
346 }
347
348 // Fetch individual post data by opening it in a new context
349 async function fetchPostData(postUrl) {
350 try {
351 console.log(`Fetching post data from: ${postUrl}`);
352
353 // Open the post URL and extract media
354 const response = await fetch(postUrl);
355 const html = await response.text();
356
357 // Extract media URLs from the HTML
358 const mediaUrls = [];
359
360 // Look for video URLs
361 const videoMatches = html.matchAll(/"video_url":"([^"]+)"/g);
362 for (const match of videoMatches) {
363 const url = match[1].replace(/\\u0026/g, '&');
364 if (url && !mediaUrls.includes(url)) {
365 mediaUrls.push({ type: 'video', url: url });
366 }
367 }
368
369 // Look for high-res image URLs
370 const imageMatches = html.matchAll(/"display_url":"([^"]+)"/g);
371 for (const match of imageMatches) {
372 const url = match[1].replace(/\\u0026/g, '&');
373 if (url && !mediaUrls.includes(url) && url.includes('instagram')) {
374 mediaUrls.push({ type: 'image', url: url });
375 }
376 }
377
378 return mediaUrls.length > 0 ? mediaUrls : null;
379 } catch (error) {
380 console.error('Error fetching post data:', error);
381 return null;
382 }
383 }
384
385 // Extract media URLs from posts
386 function extractMediaFromPosts(posts) {
387 // Return posts as-is, we'll fetch media URLs during download
388 return posts.map((edge, index) => ({
389 shortcode: edge.node.shortcode,
390 thumbnail: edge.node.thumbnail_src,
391 postUrl: edge.node.postUrl,
392 index: index
393 }));
394 }
395
396 // Download a single media file
397 async function downloadMedia(mediaItem, username, itemNumber, totalItems) {
398 try {
399 console.log(`Processing ${itemNumber}/${totalItems}: ${mediaItem.shortcode}`);
400
401 // Fetch the post to get media URLs
402 const mediaUrls = await fetchPostData(mediaItem.postUrl);
403
404 if (!mediaUrls || mediaUrls.length === 0) {
405 console.error(`No media found for ${mediaItem.shortcode}`);
406 addDownloadItem(mediaItem.thumbnail, `${mediaItem.shortcode}`, 'error - no media found');
407 return false;
408 }
409
410 // Download all media from this post
411 let successCount = 0;
412 for (let i = 0; i < mediaUrls.length; i++) {
413 const media = mediaUrls[i];
414 const extension = media.type === 'video' ? 'mp4' : 'jpg';
415 const filename = `${username}_${mediaItem.shortcode}_${i}.${extension}`;
416
417 try {
418 const response = await GM.xmlhttpRequest({
419 method: 'GET',
420 url: media.url,
421 responseType: 'blob'
422 });
423
424 // Create blob URL and trigger download
425 const blob = response.response;
426 const blobUrl = URL.createObjectURL(blob);
427
428 const a = document.createElement('a');
429 a.href = blobUrl;
430 a.download = filename;
431 document.body.appendChild(a);
432 a.click();
433 document.body.removeChild(a);
434
435 // Clean up blob URL after a delay
436 setTimeout(() => URL.revokeObjectURL(blobUrl), 1000);
437
438 addDownloadItem(mediaItem.thumbnail, filename, 'success');
439 successCount++;
440
441 // Small delay between downloads from same post
442 await new Promise(resolve => setTimeout(resolve, 300));
443 } catch (error) {
444 console.error(`Error downloading ${filename}:`, error);
445 addDownloadItem(mediaItem.thumbnail, filename, 'error');
446 }
447 }
448
449 return successCount > 0;
450 } catch (error) {
451 console.error(`Error processing ${mediaItem.shortcode}:`, error);
452 addDownloadItem(mediaItem.thumbnail, mediaItem.shortcode, 'error');
453 return false;
454 }
455 }
456
457 // Main download function
458 async function startDownload() {
459 if (isDownloading) {
460 alert('Download already in progress!');
461 return;
462 }
463
464 const username = getUsername();
465 if (!username) {
466 alert('Please navigate to an Instagram profile page to download media.');
467 return;
468 }
469
470 isDownloading = true;
471 const downloadBtn = document.querySelector('.ig-download-btn');
472 if (downloadBtn) {
473 downloadBtn.disabled = true;
474 downloadBtn.textContent = 'Downloading...';
475 }
476
477 // Create download panel
478 createDownloadPanel();
479
480 try {
481 // Step 1: Fetch user data
482 updateDownloadStatus('Fetching profile information...', 5);
483 const userData = await fetchUserData(username);
484 console.log('User data:', userData);
485
486 // Step 2: Fetch all posts
487 updateDownloadStatus('Scanning all posts...', 10);
488 const posts = await fetchAllPosts(userData.id);
489 console.log(`Found ${posts.length} posts`);
490
491 if (posts.length === 0) {
492 updateDownloadStatus('No posts found on this profile.', 100);
493 return;
494 }
495
496 // Step 3: Extract media URLs
497 updateDownloadStatus('Extracting media files...', 20);
498 const mediaItems = extractMediaFromPosts(posts);
499 console.log(`Extracted ${mediaItems.length} media items`);
500
501 if (mediaItems.length === 0) {
502 updateDownloadStatus('No media files found.', 100);
503 return;
504 }
505
506 updateDownloadStatus(`Found ${mediaItems.length} media files. Starting download...`, 25);
507
508 // Step 4: Download all media
509 let successCount = 0;
510 let errorCount = 0;
511
512 for (let i = 0; i < mediaItems.length; i++) {
513 const progress = 25 + ((i + 1) / mediaItems.length) * 75;
514 updateDownloadStatus(`Downloading ${i + 1}/${mediaItems.length} files...`, progress);
515
516 const success = await downloadMedia(mediaItems[i], username, i + 1, mediaItems.length);
517 if (success) {
518 successCount++;
519 } else {
520 errorCount++;
521 }
522
523 // Delay between downloads to avoid overwhelming the browser
524 await new Promise(resolve => setTimeout(resolve, 500));
525 }
526
527 // Final status
528 updateDownloadStatus(`Complete! Downloaded ${successCount} files. ${errorCount > 0 ? `${errorCount} failed.` : ''}`, 100);
529 console.log(`Download complete: ${successCount} success, ${errorCount} errors`);
530
531 } catch (error) {
532 console.error('Download error:', error);
533 updateDownloadStatus(`Error: ${error.message}`, 0);
534 alert(`Error downloading media: ${error.message}`);
535 } finally {
536 isDownloading = false;
537 if (downloadBtn) {
538 downloadBtn.disabled = false;
539 downloadBtn.textContent = 'Download All Media';
540 }
541 }
542 }
543
544 // Add download button to profile page
545 function addDownloadButton() {
546 // Check if we're on a profile page
547 const username = getUsername();
548 if (!username) {
549 console.log('Not on a profile page');
550 return;
551 }
552
553 // Check if button already exists
554 if (document.querySelector('.ig-download-btn')) {
555 return;
556 }
557
558 // Find the profile header section with action buttons
559 const headerSection = document.querySelector('header section');
560 if (!headerSection) {
561 console.log('Header section not found');
562 return;
563 }
564
565 // Find the button container (usually contains Follow/Message buttons)
566 const buttonContainer = headerSection.querySelector('div[class*="x6s0dn4"][class*="x78zum5"]');
567 if (!buttonContainer) {
568 console.log('Button container not found');
569 return;
570 }
571
572 // Create download button
573 const downloadBtn = document.createElement('button');
574 downloadBtn.className = 'ig-download-btn';
575 downloadBtn.textContent = 'Download All Media';
576 downloadBtn.addEventListener('click', startDownload);
577
578 // Insert button
579 buttonContainer.appendChild(downloadBtn);
580 console.log('Download button added successfully');
581 }
582
583 // Initialize the extension
584 function init() {
585 console.log('Initializing Instagram Media Downloader');
586
587 // Wait for page to load
588 if (document.readyState === 'loading') {
589 document.addEventListener('DOMContentLoaded', addDownloadButton);
590 } else {
591 addDownloadButton();
592 }
593
594 // Watch for navigation changes (Instagram is a SPA)
595 const observer = new MutationObserver(debounce(() => {
596 addDownloadButton();
597 }, 1000));
598
599 observer.observe(document.body, {
600 childList: true,
601 subtree: true
602 });
603
604 // Also listen for URL changes
605 let lastUrl = location.href;
606 new MutationObserver(() => {
607 const url = location.href;
608 if (url !== lastUrl) {
609 lastUrl = url;
610 setTimeout(addDownloadButton, 2000);
611 }
612 }).observe(document, { subtree: true, childList: true });
613 }
614
615 // Start the extension
616 init();
617
618})();