Bookmark URL Redirect Updater

Automatically updates bookmarks with their redirected URLs while preserving titles and folder locations

Size

14.4 KB

Version

1.0.1

Created

Oct 31, 2025

Updated

14 days ago

1// ==UserScript==
2// @name		Bookmark URL Redirect Updater
3// @description		Automatically updates bookmarks with their redirected URLs while preserving titles and folder locations
4// @version		1.0.1
5// @match		https://*.robomonkey.io/*
6// @icon		https://robomonkey.io/icon.png?adc3438f5fbb5315
7// @grant		GM.xmlhttpRequest
8// @grant		GM.getValue
9// @grant		GM.setValue
10// ==/UserScript==
11(function() {
12    'use strict';
13
14    // Utility function to debounce operations
15    function debounce(func, wait) {
16        let timeout;
17        return function executedFunction(...args) {
18            const later = () => {
19                clearTimeout(timeout);
20                func(...args);
21            };
22            clearTimeout(timeout);
23            timeout = setTimeout(later, wait);
24        };
25    }
26
27    // Function to detect final URL after redirects
28    async function getFinalUrl(url) {
29        try {
30            console.log(`Checking redirects for: ${url}`);
31            
32            const response = await GM.xmlhttpRequest({
33                method: 'HEAD',
34                url: url,
35                timeout: 10000,
36                anonymous: false
37            });
38
39            // The final URL after redirects
40            const finalUrl = response.finalUrl || url;
41            
42            if (finalUrl !== url) {
43                console.log(`Redirect detected: ${url} -> ${finalUrl}`);
44            }
45            
46            return {
47                original: url,
48                final: finalUrl,
49                redirected: finalUrl !== url,
50                status: response.status
51            };
52        } catch (error) {
53            console.error(`Error checking URL ${url}:`, error);
54            return {
55                original: url,
56                final: url,
57                redirected: false,
58                error: error.message,
59                status: 0
60            };
61        }
62    }
63
64    // Parse bookmarks from HTML file
65    function parseBookmarksHtml(htmlContent) {
66        const parser = new DOMParser();
67        const doc = parser.parseFromString(htmlContent, 'text/html');
68        const bookmarks = [];
69
70        // Find all bookmark links
71        const links = doc.querySelectorAll('a[href]');
72        links.forEach((link, index) => {
73            const url = link.getAttribute('href');
74            const title = link.textContent.trim();
75            
76            // Try to determine folder structure
77            let folder = 'Root';
78            let parent = link.parentElement;
79            while (parent) {
80                if (parent.tagName === 'DL') {
81                    const h3 = parent.previousElementSibling;
82                    if (h3 && h3.tagName === 'H3') {
83                        folder = h3.textContent.trim();
84                        break;
85                    }
86                }
87                parent = parent.parentElement;
88            }
89
90            bookmarks.push({
91                id: index,
92                url: url,
93                title: title,
94                folder: folder,
95                element: link
96            });
97        });
98
99        return { bookmarks, doc };
100    }
101
102    // Update bookmarks HTML with new URLs
103    function updateBookmarksHtml(doc, updates) {
104        const links = doc.querySelectorAll('a[href]');
105        let updatedCount = 0;
106
107        links.forEach((link, index) => {
108            const update = updates.find(u => u.id === index);
109            if (update && update.redirected) {
110                link.setAttribute('href', update.final);
111                updatedCount++;
112            }
113        });
114
115        return { html: doc.documentElement.outerHTML, updatedCount };
116    }
117
118    // Create UI panel
119    function createUI() {
120        const panel = document.createElement('div');
121        panel.id = 'bookmark-updater-panel';
122        panel.style.cssText = `
123            position: fixed;
124            top: 20px;
125            right: 20px;
126            width: 400px;
127            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
128            border-radius: 12px;
129            box-shadow: 0 10px 40px rgba(0,0,0,0.3);
130            z-index: 999999;
131            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
132            color: white;
133            overflow: hidden;
134        `;
135
136        panel.innerHTML = `
137            <div style="padding: 20px;">
138                <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
139                    <h2 style="margin: 0; font-size: 18px; font-weight: 600;">Bookmark URL Updater</h2>
140                    <button id="close-panel" style="background: rgba(255,255,255,0.2); border: none; color: white; width: 28px; height: 28px; border-radius: 50%; cursor: pointer; font-size: 18px; display: flex; align-items: center; justify-content: center;">×</button>
141                </div>
142                
143                <div style="background: rgba(255,255,255,0.1); border-radius: 8px; padding: 15px; margin-bottom: 15px;">
144                    <p style="margin: 0 0 10px 0; font-size: 13px; line-height: 1.5;">
145                        Export your bookmarks from Chrome (Bookmarks → Bookmark Manager → ⋮ → Export bookmarks), then upload the HTML file below.
146                    </p>
147                    <input type="file" id="bookmark-file" accept=".html" style="display: none;">
148                    <button id="select-file-btn" style="width: 100%; padding: 10px; background: white; color: #667eea; border: none; border-radius: 6px; font-weight: 600; cursor: pointer; font-size: 14px;">
149                        Select Bookmark File
150                    </button>
151                </div>
152
153                <div id="file-info" style="display: none; background: rgba(255,255,255,0.1); border-radius: 8px; padding: 12px; margin-bottom: 15px; font-size: 13px;">
154                    <div style="margin-bottom: 8px;">📁 <span id="file-name"></span></div>
155                    <div>🔖 <span id="bookmark-count"></span> bookmarks found</div>
156                </div>
157
158                <button id="scan-btn" style="width: 100%; padding: 12px; background: rgba(255,255,255,0.95); color: #667eea; border: none; border-radius: 6px; font-weight: 600; cursor: pointer; font-size: 14px; display: none; margin-bottom: 15px;">
159                    Scan for Redirects
160                </button>
161
162                <div id="progress-container" style="display: none; margin-bottom: 15px;">
163                    <div style="display: flex; justify-content: space-between; margin-bottom: 8px; font-size: 13px;">
164                        <span>Scanning...</span>
165                        <span id="progress-text">0%</span>
166                    </div>
167                    <div style="background: rgba(255,255,255,0.2); border-radius: 10px; height: 8px; overflow: hidden;">
168                        <div id="progress-bar" style="background: white; height: 100%; width: 0%; transition: width 0.3s;"></div>
169                    </div>
170                    <div id="current-url" style="margin-top: 8px; font-size: 11px; opacity: 0.8; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;"></div>
171                </div>
172
173                <div id="results" style="display: none; background: rgba(255,255,255,0.1); border-radius: 8px; padding: 15px; margin-bottom: 15px;">
174                    <div style="font-size: 14px; font-weight: 600; margin-bottom: 10px;">Scan Results</div>
175                    <div style="font-size: 13px; line-height: 1.8;">
176                        <div>✅ Total scanned: <span id="total-scanned">0</span></div>
177                        <div>🔄 Redirects found: <span id="redirects-found">0</span></div>
178                        <div>❌ Errors: <span id="errors-found">0</span></div>
179                    </div>
180                </div>
181
182                <button id="download-btn" style="width: 100%; padding: 12px; background: #10b981; color: white; border: none; border-radius: 6px; font-weight: 600; cursor: pointer; font-size: 14px; display: none;">
183                    Download Updated Bookmarks
184                </button>
185
186                <div id="instructions" style="display: none; background: rgba(16, 185, 129, 0.2); border-radius: 8px; padding: 12px; margin-top: 15px; font-size: 12px; line-height: 1.6;">
187                    <strong>Next Steps:</strong><br>
188                    1. Download the updated file<br>
189                    2. Go to Chrome Bookmarks Manager<br>
190                    3. Click ⋮ → Import bookmarks<br>
191                    4. Select the downloaded file
192                </div>
193            </div>
194        `;
195
196        document.body.appendChild(panel);
197        return panel;
198    }
199
200    // Main initialization
201    async function init() {
202        console.log('Bookmark URL Redirect Updater initialized');
203
204        // Create UI
205        const panel = createUI();
206        
207        let bookmarksData = null;
208        let scanResults = [];
209
210        // UI Elements
211        const fileInput = document.getElementById('bookmark-file');
212        const selectFileBtn = document.getElementById('select-file-btn');
213        const scanBtn = document.getElementById('scan-btn');
214        const downloadBtn = document.getElementById('download-btn');
215        const closeBtn = document.getElementById('close-panel');
216        const fileInfo = document.getElementById('file-info');
217        const progressContainer = document.getElementById('progress-container');
218        const resultsDiv = document.getElementById('results');
219        const instructionsDiv = document.getElementById('instructions');
220
221        // Close panel
222        closeBtn.addEventListener('click', () => {
223            panel.style.display = 'none';
224        });
225
226        // File selection
227        selectFileBtn.addEventListener('click', () => {
228            fileInput.click();
229        });
230
231        fileInput.addEventListener('change', async (e) => {
232            const file = e.target.files[0];
233            if (!file) return;
234
235            const reader = new FileReader();
236            reader.onload = async (event) => {
237                const htmlContent = event.target.result;
238                bookmarksData = parseBookmarksHtml(htmlContent);
239                
240                document.getElementById('file-name').textContent = file.name;
241                document.getElementById('bookmark-count').textContent = bookmarksData.bookmarks.length;
242                
243                fileInfo.style.display = 'block';
244                scanBtn.style.display = 'block';
245                
246                console.log(`Loaded ${bookmarksData.bookmarks.length} bookmarks from file`);
247            };
248            reader.readAsText(file);
249        });
250
251        // Scan for redirects
252        scanBtn.addEventListener('click', async () => {
253            if (!bookmarksData) return;
254
255            scanBtn.disabled = true;
256            scanBtn.textContent = 'Scanning...';
257            progressContainer.style.display = 'block';
258            resultsDiv.style.display = 'none';
259            downloadBtn.style.display = 'none';
260            instructionsDiv.style.display = 'none';
261
262            scanResults = [];
263            const total = bookmarksData.bookmarks.length;
264            let completed = 0;
265            let redirectsFound = 0;
266            let errorsFound = 0;
267
268            // Process bookmarks in batches to avoid overwhelming the browser
269            const batchSize = 5;
270            for (let i = 0; i < bookmarksData.bookmarks.length; i += batchSize) {
271                const batch = bookmarksData.bookmarks.slice(i, i + batchSize);
272                
273                const batchPromises = batch.map(async (bookmark) => {
274                    document.getElementById('current-url').textContent = bookmark.url;
275                    
276                    const result = await getFinalUrl(bookmark.url);
277                    completed++;
278                    
279                    if (result.redirected) redirectsFound++;
280                    if (result.error) errorsFound++;
281                    
282                    const progress = Math.round((completed / total) * 100);
283                    document.getElementById('progress-bar').style.width = progress + '%';
284                    document.getElementById('progress-text').textContent = progress + '%';
285                    
286                    return {
287                        id: bookmark.id,
288                        ...result,
289                        title: bookmark.title,
290                        folder: bookmark.folder
291                    };
292                });
293
294                const batchResults = await Promise.all(batchPromises);
295                scanResults.push(...batchResults);
296
297                // Small delay between batches
298                await new Promise(resolve => setTimeout(resolve, 100));
299            }
300
301            // Show results
302            document.getElementById('total-scanned').textContent = total;
303            document.getElementById('redirects-found').textContent = redirectsFound;
304            document.getElementById('errors-found').textContent = errorsFound;
305            
306            resultsDiv.style.display = 'block';
307            progressContainer.style.display = 'none';
308            
309            if (redirectsFound > 0) {
310                downloadBtn.style.display = 'block';
311                instructionsDiv.style.display = 'block';
312            }
313
314            scanBtn.disabled = false;
315            scanBtn.textContent = 'Scan Again';
316
317            console.log('Scan complete:', { total, redirectsFound, errorsFound });
318        });
319
320        // Download updated bookmarks
321        downloadBtn.addEventListener('click', () => {
322            const { html, updatedCount } = updateBookmarksHtml(bookmarksData.doc, scanResults);
323            
324            const blob = new Blob([html], { type: 'text/html' });
325            const url = URL.createObjectURL(blob);
326            const a = document.createElement('a');
327            a.href = url;
328            a.download = `bookmarks_updated_${Date.now()}.html`;
329            document.body.appendChild(a);
330            a.click();
331            document.body.removeChild(a);
332            URL.revokeObjectURL(url);
333
334            console.log(`Downloaded updated bookmarks file with ${updatedCount} updates`);
335        });
336
337        // Load saved state
338        const savedState = await GM.getValue('panelVisible', true);
339        if (!savedState) {
340            panel.style.display = 'none';
341        }
342
343        // Save state when closing
344        closeBtn.addEventListener('click', async () => {
345            await GM.setValue('panelVisible', false);
346        });
347    }
348
349    // Wait for page to load
350    if (document.readyState === 'loading') {
351        document.addEventListener('DOMContentLoaded', init);
352    } else {
353        init();
354    }
355})();
Bookmark URL Redirect Updater | Robomonkey