// ==UserScript== // @name Pinterest Download Fixer // @namespace https://github.com/Angel2mp3 // @version 1.0 // @description Fix Pinterest downloads with proper file extensions and pin title extraction // @author Angel2mp3 // @match https://www.pinterest.com/* // @match https://pinterest.com/* // @grant GM_xmlhttpRequest // @connect * // ==/UserScript== (function() { 'use strict'; // Detect file type from binary data function detectFileType(arr) { if (arr.length < 12) return '.jpg'; if (arr[0] === 0x89 && arr[1] === 0x50 && arr[2] === 0x4E && arr[3] === 0x47) return '.png'; if (arr[0] === 0xFF && arr[1] === 0xD8 && arr[2] === 0xFF) return '.jpg'; if (arr[0] === 0x47 && arr[1] === 0x49 && arr[2] === 0x46 && arr[3] === 0x38) return '.gif'; if (arr[0] === 0x52 && arr[1] === 0x49 && arr[2] === 0x46 && arr[3] === 0x46 && arr[8] === 0x57 && arr[9] === 0x45 && arr[10] === 0x42 && arr[11] === 0x50) return '.webp'; return '.jpg'; } // Sanitize filename function sanitizeFilename(filename) { if (!filename) return null; let sanitized = filename .replace(/[<>:"\/\\|?*]/g, '') .replace(/[\x00-\x1f\x80-\x9f]/g, '') .trim(); if (sanitized.length > 200) sanitized = sanitized.substring(0, 200); return sanitized.length > 0 ? sanitized : null; } // Generate random alphanumeric string function generateRandomString(length) { const chars = 'abcdefghijklmnopqrstuvwxyz0123456789'; let result = ''; for (let i = 0; i < length; i++) { result += chars.charAt(Math.floor(Math.random() * chars.length)); } return result; } // Extract pin title from page function extractPinTitle() { // Try title selectors const selectors = [ '[data-test-id="closeup-title"] h1', '[data-test-id="pin-title"]', 'h1[itemprop="name"]' ]; for (const sel of selectors) { const el = document.querySelector(sel); if (el?.textContent?.trim()) return sanitizeFilename(el.textContent.trim()); } // Try meta tag const meta = document.querySelector('meta[property="og:title"]'); if (meta?.content) return sanitizeFilename(meta.content.trim()); return null; } // Find main pin image URL function findMainImageUrl() { const selectors = [ 'img[elementtiming*="MainPinImage"]', 'img.hCL', 'img[fetchpriority="high"]' ]; for (const sel of selectors) { const img = document.querySelector(sel); if (img) { // Try srcset for highest quality const srcset = img.getAttribute('srcset'); if (srcset) { const best = srcset.split(',') .map(s => s.trim().split(/\s+/)) .filter(p => p[0]) .sort((a, b) => (parseInt(b[1]) || 0) - (parseInt(a[1]) || 0))[0]; if (best) return best[0]; } return img.currentSrc || img.src; } } return null; } // Download image with proper name async function downloadImage(imageUrl) { if (!imageUrl) return; try { const response = await new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'GET', url: imageUrl, responseType: 'arraybuffer', onload: r => r.status >= 200 && r.status < 300 ? resolve(r) : reject(), onerror: reject }); }); const arr = new Uint8Array(response.response); const ext = detectFileType(arr); const title = extractPinTitle(); const filename = (title || `pin-${generateRandomString(15)}`) + ext; const blob = new Blob([response.response]); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = filename; a.click(); setTimeout(() => URL.revokeObjectURL(url), 100); } catch (e) { console.error('Download failed:', e); } } // Single event listener using event delegation document.addEventListener('click', function(e) { const target = e.target.closest('[data-test-id*="download"], [aria-label*="ownload" i]'); if (!target) return; const text = (target.textContent || '').toLowerCase(); const testId = target.getAttribute('data-test-id') || ''; const ariaLabel = (target.getAttribute('aria-label') || '').toLowerCase(); if (text.includes('download') || testId.includes('download') || ariaLabel.includes('download')) { e.preventDefault(); e.stopPropagation(); const imageUrl = findMainImageUrl(); if (imageUrl) downloadImage(imageUrl); } }, true); console.log('Pinterest Download Fix v3.0 loaded'); })();