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