Instagram Media Downloader

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