Synced Tabs Analyzer & Categorizer

Counts and categorizes synced tabs from Chrome history

Size

14.0 KB

Version

1.0.1

Created

Jan 28, 2026

Updated

6 days ago

1// ==UserScript==
2// @name		Synced Tabs Analyzer & Categorizer
3// @description		Counts and categorizes synced tabs from Chrome history
4// @version		1.0.1
5// @match		chrome://history/*
6// @icon		https://robomonkey.io/favicon.ico
7// ==/UserScript==
8(function() {
9    'use strict';
10
11    console.log('Synced Tabs Analyzer & Categorizer loaded');
12
13    // Debounce function to prevent excessive calls
14    function debounce(func, wait) {
15        let timeout;
16        return function executedFunction(...args) {
17            const later = () => {
18                clearTimeout(timeout);
19                func(...args);
20            };
21            clearTimeout(timeout);
22            timeout = setTimeout(later, wait);
23        };
24    }
25
26    // Function to navigate to synced tabs if not already there
27    function navigateToSyncedTabs() {
28        const currentUrl = window.location.href;
29        if (!currentUrl.includes('syncedTabs')) {
30            console.log('Navigating to synced tabs page...');
31            window.location.href = 'chrome://history/syncedTabs';
32        }
33    }
34
35    // Function to extract tab information from the page
36    function extractTabsInfo() {
37        console.log('Extracting tabs information...');
38        
39        // Try multiple selectors to find synced tabs
40        const tabElements = document.querySelectorAll('history-synced-device-card a[href], history-synced-device-card .website-title, history-synced-device-card .item-title, [id*="synced"] a, [class*="synced"] a, [class*="tab"] a[href]');
41        
42        console.log(`Found ${tabElements.length} potential tab elements`);
43        
44        const tabs = [];
45        const seenUrls = new Set();
46        
47        tabElements.forEach((element, index) => {
48            let title = '';
49            let url = '';
50            
51            // Extract title
52            if (element.textContent) {
53                title = element.textContent.trim();
54            }
55            
56            // Extract URL
57            if (element.href) {
58                url = element.href;
59            } else if (element.getAttribute('href')) {
60                url = element.getAttribute('href');
61            }
62            
63            // Only add if we have valid data and haven't seen this URL
64            if ((title || url) && !seenUrls.has(url)) {
65                tabs.push({
66                    title: title || 'Untitled',
67                    url: url || 'No URL',
68                    index: index
69                });
70                if (url) seenUrls.add(url);
71            }
72        });
73        
74        console.log(`Extracted ${tabs.length} unique tabs`);
75        return tabs;
76    }
77
78    // Function to categorize tabs using AI
79    async function categorizeTabs(tabs) {
80        console.log('Categorizing tabs with AI...');
81        
82        if (tabs.length === 0) {
83            console.log('No tabs to categorize');
84            return { categories: {}, totalTabs: 0 };
85        }
86        
87        try {
88            // Prepare tab data for AI
89            const tabsData = tabs.map(tab => ({
90                title: tab.title,
91                url: tab.url
92            }));
93            
94            const prompt = `Analyze these browser tabs and categorize them into meaningful categories. 
95            
96Tabs data:
97${JSON.stringify(tabsData, null, 2)}
98
99Please categorize each tab into appropriate categories like: Social Media, Shopping, News, Development, Entertainment, Productivity, Education, etc.`;
100
101            const categorization = await RM.aiCall(prompt, {
102                type: "json_schema",
103                json_schema: {
104                    name: "tab_categorization",
105                    schema: {
106                        type: "object",
107                        properties: {
108                            categories: {
109                                type: "object",
110                                additionalProperties: {
111                                    type: "object",
112                                    properties: {
113                                        count: { type: "number" },
114                                        tabs: {
115                                            type: "array",
116                                            items: {
117                                                type: "object",
118                                                properties: {
119                                                    title: { type: "string" },
120                                                    url: { type: "string" }
121                                                },
122                                                required: ["title", "url"]
123                                            }
124                                        }
125                                    },
126                                    required: ["count", "tabs"]
127                                }
128                            },
129                            totalTabs: { type: "number" }
130                        },
131                        required: ["categories", "totalTabs"]
132                    }
133                }
134            });
135            
136            console.log('Categorization complete:', categorization);
137            return categorization;
138            
139        } catch (error) {
140            console.error('Error categorizing tabs:', error);
141            return { categories: {}, totalTabs: tabs.length, error: error.message };
142        }
143    }
144
145    // Function to display results
146    function displayResults(tabs, categorization) {
147        console.log('Displaying results...');
148        
149        // Remove existing results panel if any
150        const existingPanel = document.getElementById('tabs-analyzer-panel');
151        if (existingPanel) {
152            existingPanel.remove();
153        }
154        
155        // Create results panel
156        const panel = document.createElement('div');
157        panel.id = 'tabs-analyzer-panel';
158        panel.style.cssText = `
159            position: fixed;
160            top: 20px;
161            right: 20px;
162            width: 400px;
163            max-height: 80vh;
164            overflow-y: auto;
165            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
166            color: white;
167            padding: 20px;
168            border-radius: 12px;
169            box-shadow: 0 10px 40px rgba(0,0,0,0.3);
170            z-index: 999999;
171            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
172        `;
173        
174        let html = `
175            <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;">
176                <h2 style="margin: 0; font-size: 20px; font-weight: 600;">📊 Synced Tabs Analysis</h2>
177                <button id="close-analyzer-panel" style="background: rgba(255,255,255,0.2); border: none; color: white; width: 30px; height: 30px; border-radius: 50%; cursor: pointer; font-size: 18px;">×</button>
178            </div>
179            <div style="background: rgba(255,255,255,0.15); padding: 15px; border-radius: 8px; margin-bottom: 15px;">
180                <div style="font-size: 32px; font-weight: bold; margin-bottom: 5px;">${categorization.totalTabs || tabs.length}</div>
181                <div style="font-size: 14px; opacity: 0.9;">Total Synced Tabs</div>
182            </div>
183        `;
184        
185        if (categorization.error) {
186            html += `
187                <div style="background: rgba(255,255,255,0.15); padding: 15px; border-radius: 8px; margin-bottom: 10px;">
188                    <div style="font-size: 14px; color: #ffcccc;">⚠️ Error: ${categorization.error}</div>
189                </div>
190            `;
191        }
192        
193        if (categorization.categories && Object.keys(categorization.categories).length > 0) {
194            html += '<h3 style="font-size: 16px; margin: 15px 0 10px 0; font-weight: 600;">Categories:</h3>';
195            
196            // Sort categories by count
197            const sortedCategories = Object.entries(categorization.categories)
198                .sort((a, b) => b[1].count - a[1].count);
199            
200            sortedCategories.forEach(([categoryName, categoryData]) => {
201                const percentage = ((categoryData.count / categorization.totalTabs) * 100).toFixed(1);
202                html += `
203                    <div style="background: rgba(255,255,255,0.15); padding: 12px; border-radius: 8px; margin-bottom: 8px;">
204                        <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
205                            <div style="font-weight: 600; font-size: 14px;">${categoryName}</div>
206                            <div style="background: rgba(255,255,255,0.25); padding: 4px 10px; border-radius: 12px; font-size: 12px; font-weight: 600;">
207                                ${categoryData.count} (${percentage}%)
208                            </div>
209                        </div>
210                        <div style="background: rgba(255,255,255,0.1); height: 6px; border-radius: 3px; overflow: hidden;">
211                            <div style="background: white; height: 100%; width: ${percentage}%; border-radius: 3px;"></div>
212                        </div>
213                        <details style="margin-top: 8px;">
214                            <summary style="cursor: pointer; font-size: 12px; opacity: 0.8;">Show tabs</summary>
215                            <div style="margin-top: 8px; font-size: 12px; opacity: 0.9;">
216                                ${categoryData.tabs.map(tab => `
217                                    <div style="padding: 4px 0; border-bottom: 1px solid rgba(255,255,255,0.1);">
218                                        <div style="font-weight: 500;">${tab.title}</div>
219                                        <div style="opacity: 0.7; font-size: 11px; word-break: break-all;">${tab.url}</div>
220                                    </div>
221                                `).join('')}
222                            </div>
223                        </details>
224                    </div>
225                `;
226            });
227        }
228        
229        panel.innerHTML = html;
230        document.body.appendChild(panel);
231        
232        // Add close button functionality
233        const closeBtn = document.getElementById('close-analyzer-panel');
234        if (closeBtn) {
235            closeBtn.addEventListener('click', () => {
236                panel.remove();
237            });
238        }
239        
240        console.log('Results displayed successfully');
241    }
242
243    // Function to show loading indicator
244    function showLoading() {
245        const existingLoader = document.getElementById('tabs-analyzer-loader');
246        if (existingLoader) return;
247        
248        const loader = document.createElement('div');
249        loader.id = 'tabs-analyzer-loader';
250        loader.style.cssText = `
251            position: fixed;
252            top: 20px;
253            right: 20px;
254            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
255            color: white;
256            padding: 15px 20px;
257            border-radius: 8px;
258            box-shadow: 0 4px 12px rgba(0,0,0,0.2);
259            z-index: 999999;
260            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
261            font-size: 14px;
262        `;
263        loader.innerHTML = '🔄 Analyzing synced tabs...';
264        document.body.appendChild(loader);
265    }
266
267    function hideLoading() {
268        const loader = document.getElementById('tabs-analyzer-loader');
269        if (loader) {
270            loader.remove();
271        }
272    }
273
274    // Main analysis function
275    async function analyzeSyncedTabs() {
276        console.log('Starting synced tabs analysis...');
277        
278        showLoading();
279        
280        // Wait a bit for the page to fully load
281        await new Promise(resolve => setTimeout(resolve, 2000));
282        
283        const tabs = extractTabsInfo();
284        
285        if (tabs.length === 0) {
286            console.log('No tabs found. The page might not be fully loaded or the structure has changed.');
287            hideLoading();
288            
289            // Show a message to the user
290            const panel = document.createElement('div');
291            panel.style.cssText = `
292                position: fixed;
293                top: 20px;
294                right: 20px;
295                background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
296                color: white;
297                padding: 20px;
298                border-radius: 12px;
299                box-shadow: 0 10px 40px rgba(0,0,0,0.3);
300                z-index: 999999;
301                font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
302            `;
303            panel.innerHTML = `
304                <h3 style="margin: 0 0 10px 0;">⚠️ No Synced Tabs Found</h3>
305                <p style="margin: 0; font-size: 14px;">Make sure you're on the Synced Tabs page and have synced devices.</p>
306            `;
307            document.body.appendChild(panel);
308            setTimeout(() => panel.remove(), 5000);
309            return;
310        }
311        
312        const categorization = await categorizeTabs(tabs);
313        
314        hideLoading();
315        displayResults(tabs, categorization);
316    }
317
318    // Initialize the extension
319    async function init() {
320        console.log('Initializing Synced Tabs Analyzer...');
321        
322        // Check if we're on the history page
323        if (!window.location.href.includes('chrome://history')) {
324            console.log('Not on history page, skipping...');
325            return;
326        }
327        
328        // Navigate to synced tabs if needed
329        if (!window.location.href.includes('syncedTabs')) {
330            navigateToSyncedTabs();
331            return;
332        }
333        
334        // Wait for the page to be ready
335        if (document.readyState === 'loading') {
336            document.addEventListener('DOMContentLoaded', () => {
337                setTimeout(analyzeSyncedTabs, 1000);
338            });
339        } else {
340            setTimeout(analyzeSyncedTabs, 1000);
341        }
342        
343        // Re-analyze when the page changes (debounced)
344        const debouncedAnalyze = debounce(analyzeSyncedTabs, 2000);
345        const observer = new MutationObserver(debouncedAnalyze);
346        observer.observe(document.body, {
347            childList: true,
348            subtree: true
349        });
350    }
351
352    // Start the extension
353    init();
354})();
Synced Tabs Analyzer & Categorizer | Robomonkey