Instagram Highlights Downloader

Download all story highlights from Instagram profiles with one click

Size

11.6 KB

Version

1.0.1

Created

Jan 21, 2026

Updated

27 days ago

1// ==UserScript==
2// @name		Instagram Highlights Downloader
3// @description		Download all story highlights from Instagram profiles with one click
4// @version		1.0.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 Highlights Downloader initialized');
12
13    // Utility function to wait for element
14    function waitForElement(selector, timeout = 10000) {
15        return new Promise((resolve, reject) => {
16            if (document.querySelector(selector)) {
17                return resolve(document.querySelector(selector));
18            }
19
20            const observer = new MutationObserver(() => {
21                if (document.querySelector(selector)) {
22                    observer.disconnect();
23                    resolve(document.querySelector(selector));
24                }
25            });
26
27            observer.observe(document.body, {
28                childList: true,
29                subtree: true
30            });
31
32            setTimeout(() => {
33                observer.disconnect();
34                reject(new Error('Element not found: ' + selector));
35            }, timeout);
36        });
37    }
38
39    // Function to download a file
40    async function downloadFile(url, filename) {
41        try {
42            const response = await GM.xmlhttpRequest({
43                method: 'GET',
44                url: url,
45                responseType: 'blob'
46            });
47
48            const blob = response.response;
49            const blobUrl = URL.createObjectURL(blob);
50            const a = document.createElement('a');
51            a.href = blobUrl;
52            a.download = filename;
53            document.body.appendChild(a);
54            a.click();
55            document.body.removeChild(a);
56            URL.revokeObjectURL(blobUrl);
57            console.log('Downloaded:', filename);
58        } catch (error) {
59            console.error('Error downloading file:', error);
60        }
61    }
62
63    // Function to extract highlight data from Instagram's internal data
64    async function getHighlightsData(username) {
65        try {
66            // Try to get data from Instagram's internal state
67            const scripts = document.querySelectorAll('script[type="application/ld+json"]');
68            for (const script of scripts) {
69                try {
70                    const data = JSON.parse(script.textContent);
71                    console.log('Found JSON-LD data:', data);
72                } catch (e) {}
73            }
74
75            // Look for highlights in the page
76            const highlightElements = document.querySelectorAll('ul._acay li._acaz');
77            console.log('Found highlight elements:', highlightElements.length);
78            
79            return Array.from(highlightElements);
80        } catch (error) {
81            console.error('Error getting highlights data:', error);
82            return [];
83        }
84    }
85
86    // Function to click and extract stories from a highlight
87    async function extractStoriesFromHighlight(highlightElement) {
88        try {
89            // Click the highlight to open it
90            const clickableElement = highlightElement.querySelector('div.x1i10hfl, a');
91            if (!clickableElement) {
92                console.error('No clickable element found in highlight');
93                return [];
94            }
95
96            console.log('Clicking highlight...');
97            clickableElement.click();
98
99            // Wait for the story viewer to open
100            await new Promise(resolve => setTimeout(resolve, 2000));
101
102            const stories = [];
103            let hasMoreStories = true;
104            let storyCount = 0;
105
106            while (hasMoreStories && storyCount < 100) {
107                // Find the current story media (image or video)
108                const videoElement = document.querySelector('video[class*="x1lliihq"]');
109                const imageElement = document.querySelector('img[class*="x5yr21d"][style*="object-fit"]');
110
111                if (videoElement && videoElement.src) {
112                    console.log('Found video story:', videoElement.src);
113                    stories.push({ type: 'video', url: videoElement.src });
114                } else if (imageElement && imageElement.src) {
115                    console.log('Found image story:', imageElement.src);
116                    stories.push({ type: 'image', url: imageElement.src });
117                }
118
119                storyCount++;
120
121                // Try to click next button
122                const nextButton = document.querySelector('button[aria-label="Next"], button[aria-label="Next story"]');
123                if (nextButton) {
124                    nextButton.click();
125                    await new Promise(resolve => setTimeout(resolve, 1500));
126                } else {
127                    hasMoreStories = false;
128                }
129            }
130
131            // Close the story viewer
132            const closeButton = document.querySelector('button[aria-label="Close"], svg[aria-label="Close"]');
133            if (closeButton) {
134                if (closeButton.tagName === 'BUTTON') {
135                    closeButton.click();
136                } else {
137                    closeButton.closest('button')?.click();
138                }
139            }
140
141            await new Promise(resolve => setTimeout(resolve, 1000));
142
143            return stories;
144        } catch (error) {
145            console.error('Error extracting stories from highlight:', error);
146            return [];
147        }
148    }
149
150    // Main download function
151    async function downloadAllHighlights() {
152        try {
153            const button = document.getElementById('rm-download-highlights-btn');
154            if (button) {
155                button.textContent = 'Downloading...';
156                button.disabled = true;
157            }
158
159            // Get username from URL
160            const username = window.location.pathname.split('/')[1];
161            console.log('Downloading highlights for:', username);
162
163            // Get all highlights
164            const highlightElements = await getHighlightsData(username);
165            
166            if (highlightElements.length === 0) {
167                alert('No highlights found on this profile.');
168                if (button) {
169                    button.textContent = 'Download All Highlights';
170                    button.disabled = false;
171                }
172                return;
173            }
174
175            console.log(`Found ${highlightElements.length} highlights`);
176
177            let totalDownloaded = 0;
178
179            // Process each highlight
180            for (let i = 0; i < highlightElements.length; i++) {
181                const highlight = highlightElements[i];
182                console.log(`Processing highlight ${i + 1}/${highlightElements.length}`);
183
184                if (button) {
185                    button.textContent = `Processing ${i + 1}/${highlightElements.length}...`;
186                }
187
188                const stories = await extractStoriesFromHighlight(highlight);
189                
190                // Download each story
191                for (let j = 0; j < stories.length; j++) {
192                    const story = stories[j];
193                    const extension = story.type === 'video' ? 'mp4' : 'jpg';
194                    const filename = `${username}_highlight_${i + 1}_story_${j + 1}.${extension}`;
195                    await downloadFile(story.url, filename);
196                    totalDownloaded++;
197                    await new Promise(resolve => setTimeout(resolve, 500));
198                }
199
200                // Wait between highlights
201                await new Promise(resolve => setTimeout(resolve, 1000));
202            }
203
204            alert(`Successfully downloaded ${totalDownloaded} stories from ${highlightElements.length} highlights!`);
205            
206            if (button) {
207                button.textContent = 'Download All Highlights';
208                button.disabled = false;
209            }
210        } catch (error) {
211            console.error('Error downloading highlights:', error);
212            alert('Error downloading highlights. Please check the console for details.');
213            const button = document.getElementById('rm-download-highlights-btn');
214            if (button) {
215                button.textContent = 'Download All Highlights';
216                button.disabled = false;
217            }
218        }
219    }
220
221    // Function to create and add the download button
222    function addDownloadButton() {
223        // Check if we're on a profile page
224        const isProfilePage = /^\/[^\/]+\/?$/.test(window.location.pathname);
225        if (!isProfilePage) {
226            console.log('Not on a profile page');
227            return;
228        }
229
230        // Check if button already exists
231        if (document.getElementById('rm-download-highlights-btn')) {
232            return;
233        }
234
235        // Wait for the profile header to load
236        waitForElement('header section').then(() => {
237            // Find the profile action buttons area
238            const profileSection = document.querySelector('header section');
239            if (!profileSection) {
240                console.log('Profile section not found');
241                return;
242            }
243
244            // Create the download button
245            const downloadButton = document.createElement('button');
246            downloadButton.id = 'rm-download-highlights-btn';
247            downloadButton.textContent = 'Download All Highlights';
248            downloadButton.style.cssText = `
249                background: linear-gradient(45deg, #f09433 0%, #e6683c 25%, #dc2743 50%, #cc2366 75%, #bc1888 100%);
250                color: white;
251                border: none;
252                padding: 8px 16px;
253                border-radius: 8px;
254                font-weight: 600;
255                font-size: 14px;
256                cursor: pointer;
257                margin-left: 8px;
258                transition: opacity 0.2s;
259                font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
260            `;
261
262            downloadButton.addEventListener('mouseenter', () => {
263                downloadButton.style.opacity = '0.8';
264            });
265
266            downloadButton.addEventListener('mouseleave', () => {
267                downloadButton.style.opacity = '1';
268            });
269
270            downloadButton.addEventListener('click', downloadAllHighlights);
271
272            // Insert the button next to other profile buttons
273            const buttonContainer = profileSection.querySelector('div[class*="x1iyjqo2"]');
274            if (buttonContainer) {
275                buttonContainer.appendChild(downloadButton);
276                console.log('Download button added to profile');
277            } else {
278                // Fallback: add to profile section
279                profileSection.appendChild(downloadButton);
280                console.log('Download button added to profile section (fallback)');
281            }
282        }).catch(error => {
283            console.error('Error adding download button:', error);
284        });
285    }
286
287    // Initialize
288    function init() {
289        console.log('Initializing Instagram Highlights Downloader');
290        
291        // Add button on page load
292        addDownloadButton();
293
294        // Re-add button on navigation (Instagram is a SPA)
295        let lastUrl = location.href;
296        new MutationObserver(() => {
297            const url = location.href;
298            if (url !== lastUrl) {
299                lastUrl = url;
300                console.log('URL changed to:', url);
301                setTimeout(addDownloadButton, 2000);
302            }
303        }).observe(document.body, { subtree: true, childList: true });
304    }
305
306    // Start when DOM is ready
307    if (document.readyState === 'loading') {
308        document.addEventListener('DOMContentLoaded', init);
309    } else {
310        init();
311    }
312})();
Instagram Highlights Downloader | Robomonkey