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

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