Pinterest Pin Sorter by Saves

Sort Pinterest pins by number of saves in descending order

Size

14.9 KB

Version

1.1.1

Created

Apr 8, 2026

Updated

9 days ago

1// ==UserScript==
2// @name		Pinterest Pin Sorter by Saves
3// @description		Sort Pinterest pins by number of saves in descending order
4// @version		1.1.1
5// @match		https://*.pinterest.com/*
6// @icon		https://s.pinimg.com/webapp/favicon_48x48-7470a30d.png
7// ==/UserScript==
8(function() {
9    'use strict';
10
11    console.log('Pinterest Pin Sorter by Saves - Extension loaded');
12
13    let sortingInProgress = false;
14    let lastSortTime = 0;
15    const SORT_DEBOUNCE_MS = 1000;
16
17    // Debounce function to prevent excessive sorting
18    function debounce(func, wait) {
19        let timeout;
20        return function executedFunction(...args) {
21            const later = () => {
22                clearTimeout(timeout);
23                func(...args);
24            };
25            clearTimeout(timeout);
26            timeout = setTimeout(later, wait);
27        };
28    }
29
30    // Parse save count from text (handles K, M, Y suffixes)
31    function parseSaveCount(text) {
32        if (!text) return 0;
33        
34        text = text.trim().toUpperCase();
35        const match = text.match(/(\d+(?:\.\d+)?)\s*([KMY])?/);
36        
37        if (!match) return 0;
38        
39        let number = parseFloat(match[1]);
40        const suffix = match[2];
41        
42        if (suffix === 'K') number *= 1000;
43        else if (suffix === 'M') number *= 1000000;
44        else if (suffix === 'Y') number *= 1000000000;
45        
46        return number;
47    }
48
49    // Extract save count from a pin element
50    function getSaveCount(pinElement) {
51        try {
52            // Method 1: Check for pin-stats (from another extension)
53            const pinStats = pinElement.querySelector('.pin-stats');
54            if (pinStats) {
55                const firstStatItem = pinStats.querySelector('.stat-item span:not(.hover-text)');
56                if (firstStatItem) {
57                    const count = parseSaveCount(firstStatItem.textContent);
58                    console.log('Found save count from pin-stats:', count);
59                    return count;
60                }
61            }
62
63            // Method 2: Look for save button with count
64            const saveButtons = pinElement.querySelectorAll('button[aria-label*="save" i], button[aria-label*="Save"]');
65            for (const button of saveButtons) {
66                const label = button.getAttribute('aria-label') || '';
67                const match = label.match(/(\d+[kKmM]?)\s*save/i);
68                if (match) {
69                    const count = parseSaveCount(match[1]);
70                    console.log('Found save count from button:', count);
71                    return count;
72                }
73            }
74
75            // Method 3: Look for any text with "saves" pattern
76            const allText = pinElement.textContent;
77            const saveMatch = allText.match(/(\d+[kKmM]?)\s*saves?/i);
78            if (saveMatch) {
79                const count = parseSaveCount(saveMatch[1]);
80                console.log('Found save count from text:', count);
81                return count;
82            }
83
84            console.log('No save count found for pin');
85            return 0;
86        } catch (error) {
87            console.error('Error extracting save count:', error);
88            return 0;
89        }
90    }
91
92    // Sort pins by save count
93    function sortPins() {
94        if (sortingInProgress) {
95            console.log('Sorting already in progress, skipping...');
96            return;
97        }
98
99        const now = Date.now();
100        if (now - lastSortTime < SORT_DEBOUNCE_MS) {
101            console.log('Sorting too soon after last sort, skipping...');
102            return;
103        }
104
105        sortingInProgress = true;
106        lastSortTime = now;
107
108        try {
109            console.log('Starting pin sorting...');
110
111            // Find the masonry container
112            const masonryContainer = document.querySelector('[data-test-id="masonry-container"]');
113            if (!masonryContainer) {
114                console.log('Masonry container not found');
115                sortingInProgress = false;
116                return;
117            }
118
119            // Find all pin list items
120            const pinListItems = Array.from(masonryContainer.querySelectorAll('[data-grid-item="true"]'));
121            
122            if (pinListItems.length === 0) {
123                console.log('No pins found to sort');
124                sortingInProgress = false;
125                return;
126            }
127
128            console.log(`Found ${pinListItems.length} pins to sort`);
129
130            // Extract save counts and create sortable array
131            const pinsWithCounts = pinListItems.map(pinItem => {
132                const pinElement = pinItem.querySelector('[data-test-id="pin"]');
133                const saveCount = getSaveCount(pinItem);
134                return {
135                    element: pinItem,
136                    saveCount: saveCount,
137                    pinId: pinElement?.getAttribute('data-test-pin-id') || 'unknown'
138                };
139            });
140
141            // Sort by save count (descending)
142            pinsWithCounts.sort((a, b) => b.saveCount - a.saveCount);
143
144            console.log('Sorted pins by save count:');
145            pinsWithCounts.forEach((pin, index) => {
146                console.log(`  ${index + 1}. Pin ${pin.pinId}: ${pin.saveCount} saves`);
147            });
148
149            // Find the parent container that holds all pins
150            const pinListContainer = masonryContainer.querySelector('[role="list"]');
151            if (!pinListContainer) {
152                console.log('Pin list container not found');
153                sortingInProgress = false;
154                return;
155            }
156
157            // Reorder the DOM elements
158            pinsWithCounts.forEach(pin => {
159                pinListContainer.appendChild(pin.element);
160            });
161
162            console.log('Pins sorted successfully!');
163
164            // Add visual indicator
165            addSortIndicator(pinsWithCounts.length);
166
167        } catch (error) {
168            console.error('Error sorting pins:', error);
169        } finally {
170            sortingInProgress = false;
171        }
172    }
173
174    // Add a visual indicator showing pins are sorted
175    function addSortIndicator(pinCount) {
176        // Remove existing indicator
177        const existing = document.getElementById('pin-sort-indicator');
178        if (existing) existing.remove();
179
180        const indicator = document.createElement('div');
181        indicator.id = 'pin-sort-indicator';
182        indicator.textContent = `✓ Sorted ${pinCount} pins by saves`;
183        indicator.style.cssText = `
184            position: fixed;
185            top: 80px;
186            right: 20px;
187            background: linear-gradient(135deg, #E60023 0%, #C5001A 100%);
188            color: white;
189            padding: 12px 20px;
190            border-radius: 24px;
191            font-size: 14px;
192            font-weight: 600;
193            box-shadow: 0 4px 12px rgba(230, 0, 35, 0.3);
194            z-index: 10000;
195            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
196            animation: slideIn 0.3s ease-out;
197        `;
198
199        // Add animation
200        const style = document.createElement('style');
201        style.textContent = `
202            @keyframes slideIn {
203                from {
204                    transform: translateX(400px);
205                    opacity: 0;
206                }
207                to {
208                    transform: translateX(0);
209                    opacity: 1;
210                }
211            }
212        `;
213        document.head.appendChild(style);
214
215        document.body.appendChild(indicator);
216
217        // Remove after 3 seconds
218        setTimeout(() => {
219            indicator.style.transition = 'opacity 0.3s ease-out';
220            indicator.style.opacity = '0';
221            setTimeout(() => indicator.remove(), 300);
222        }, 3000);
223    }
224
225    // Debounced sort function
226    const debouncedSort = debounce(sortPins, SORT_DEBOUNCE_MS);
227
228    // Auto-scroll function to load more pins
229    async function autoScrollAndSort(scrollCount = 10) {
230        console.log(`Starting auto-scroll: will scroll ${scrollCount} times to load more pins...`);
231        
232        // Show loading indicator
233        showLoadingIndicator();
234        
235        let scrollsCompleted = 0;
236        const scrollDelay = 1500; // Wait 1.5 seconds between scrolls to allow pins to load
237        
238        return new Promise((resolve) => {
239            const scrollInterval = setInterval(() => {
240                // Scroll down by viewport height
241                window.scrollBy({
242                    top: window.innerHeight,
243                    behavior: 'smooth'
244                });
245                
246                scrollsCompleted++;
247                console.log(`Scroll ${scrollsCompleted}/${scrollCount} completed`);
248                
249                // Update loading indicator
250                updateLoadingIndicator(scrollsCompleted, scrollCount);
251                
252                if (scrollsCompleted >= scrollCount) {
253                    clearInterval(scrollInterval);
254                    console.log('Auto-scroll completed, waiting for final pins to load...');
255                    
256                    // Wait a bit more for the last batch of pins to load
257                    setTimeout(() => {
258                        hideLoadingIndicator();
259                        console.log('Starting final sort...');
260                        sortPins();
261                        resolve();
262                    }, 2000);
263                }
264            }, scrollDelay);
265        });
266    }
267
268    // Show loading indicator during auto-scroll
269    function showLoadingIndicator() {
270        const indicator = document.createElement('div');
271        indicator.id = 'pin-scroll-indicator';
272        indicator.innerHTML = `
273            <div style="margin-bottom: 8px;">🔄 Loading more pins...</div>
274            <div id="scroll-progress" style="font-size: 12px; opacity: 0.9;">Scroll 0/10</div>
275        `;
276        indicator.style.cssText = `
277            position: fixed;
278            top: 80px;
279            right: 20px;
280            background: linear-gradient(135deg, #E60023 0%, #C5001A 100%);
281            color: white;
282            padding: 16px 24px;
283            border-radius: 24px;
284            font-size: 14px;
285            font-weight: 600;
286            box-shadow: 0 4px 12px rgba(230, 0, 35, 0.3);
287            z-index: 10000;
288            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
289            animation: slideIn 0.3s ease-out;
290        `;
291        document.body.appendChild(indicator);
292    }
293
294    // Update loading indicator progress
295    function updateLoadingIndicator(current, total) {
296        const progressElement = document.getElementById('scroll-progress');
297        if (progressElement) {
298            progressElement.textContent = `Scroll ${current}/${total}`;
299        }
300    }
301
302    // Hide loading indicator
303    function hideLoadingIndicator() {
304        const indicator = document.getElementById('pin-scroll-indicator');
305        if (indicator) {
306            indicator.style.transition = 'opacity 0.3s ease-out';
307            indicator.style.opacity = '0';
308            setTimeout(() => indicator.remove(), 300);
309        }
310    }
311
312    // Observe DOM changes to detect when new pins are loaded
313    function observePinterest() {
314        const observer = new MutationObserver(debounce((mutations) => {
315            // Check if pins were added or modified
316            const hasPinChanges = mutations.some(mutation => {
317                return Array.from(mutation.addedNodes).some(node => {
318                    if (node.nodeType === 1) {
319                        return node.matches('[data-grid-item="true"]') || 
320                               node.querySelector('[data-grid-item="true"]') ||
321                               node.matches('.pin-stats') ||
322                               node.querySelector('.pin-stats');
323                    }
324                    return false;
325                });
326            });
327
328            if (hasPinChanges) {
329                console.log('New pins detected, scheduling sort...');
330                debouncedSort();
331            }
332        }, 500));
333
334        // Observe the entire document for changes
335        observer.observe(document.body, {
336            childList: true,
337            subtree: true
338        });
339
340        console.log('Pinterest observer started');
341    }
342
343    // Initialize the extension
344    function init() {
345        console.log('Initializing Pinterest Pin Sorter...');
346
347        // Wait for the page to be ready
348        if (document.readyState === 'loading') {
349            document.addEventListener('DOMContentLoaded', init);
350            return;
351        }
352
353        // Wait a bit for Pinterest to load, then add the activation button
354        setTimeout(() => {
355            addActivationButton();
356            observePinterest();
357        }, 2000);
358
359        // Also sort on scroll (when new pins load)
360        let scrollTimeout;
361        window.addEventListener('scroll', () => {
362            clearTimeout(scrollTimeout);
363            scrollTimeout = setTimeout(() => {
364                debouncedSort();
365            }, 1000);
366        });
367
368        console.log('Pinterest Pin Sorter initialized');
369    }
370
371    // Add activation button to the page
372    function addActivationButton() {
373        // Remove existing button if any
374        const existing = document.getElementById('pin-sorter-button');
375        if (existing) existing.remove();
376
377        const button = document.createElement('button');
378        button.id = 'pin-sorter-button';
379        button.innerHTML = '📊 Sort Pins by Saves';
380        button.style.cssText = `
381            position: fixed;
382            top: 80px;
383            right: 20px;
384            background: linear-gradient(135deg, #E60023 0%, #C5001A 100%);
385            color: white;
386            padding: 12px 20px;
387            border: none;
388            border-radius: 24px;
389            font-size: 14px;
390            font-weight: 600;
391            box-shadow: 0 4px 12px rgba(230, 0, 35, 0.3);
392            z-index: 10000;
393            cursor: pointer;
394            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
395            transition: transform 0.2s, box-shadow 0.2s;
396        `;
397
398        // Add hover effect
399        button.addEventListener('mouseenter', () => {
400            button.style.transform = 'translateY(-2px)';
401            button.style.boxShadow = '0 6px 16px rgba(230, 0, 35, 0.4)';
402        });
403
404        button.addEventListener('mouseleave', () => {
405            button.style.transform = 'translateY(0)';
406            button.style.boxShadow = '0 4px 12px rgba(230, 0, 35, 0.3)';
407        });
408
409        // Add click handler
410        button.addEventListener('click', () => {
411            button.remove();
412            autoScrollAndSort(10);
413        });
414
415        document.body.appendChild(button);
416        console.log('Activation button added');
417    }
418
419    // Start the extension
420    init();
421
422})();