您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Select and delete pins with modern panel and progress bar
// ==UserScript== // @name Pinterest Mass Deleter // @namespace http://tampermonkey.net/ // @version 0.1 // @description Select and delete pins with modern panel and progress bar // @author HorrorAmphibian // @icon https://s.pinimg.com/webapp/favicon_48x48-7470a30d.png // @match https://www.pinterest.*/* // @match https://*.pinterest.com/* // @license MIT // @grant none // ==/UserScript== (function () { 'use strict'; let selectionMode = false; let selectedPins = new Set(); // ---- Get CSRF token ---- function getCSRFToken() { const match = document.cookie.match(/csrftoken=([^;]+)/); return match ? match[1] : null; } // ---- Create side panel ---- function createPanel() { if (document.getElementById('massDeletePanel')) return; const panel = document.createElement('div'); panel.id = 'massDeletePanel'; panel.style.position = 'fixed'; panel.style.top = '80px'; panel.style.right = '20px'; panel.style.width = '220px'; panel.style.background = '#fff'; panel.style.border = '1px solid #ccc'; panel.style.borderRadius = '12px'; panel.style.boxShadow = '0 4px 12px rgba(0,0,0,0.15)'; panel.style.padding = '15px'; panel.style.zIndex = 9999; panel.style.fontFamily = 'Arial, sans-serif'; panel.style.display = 'flex'; panel.style.flexDirection = 'column'; panel.style.gap = '10px'; const title = document.createElement('div'); title.textContent = '📌 Pinterest Mass Deleter'; title.style.fontWeight = 'bold'; title.style.fontSize = '16px'; title.style.textAlign = 'center'; panel.appendChild(title); const toggleBtn = document.createElement('button'); toggleBtn.textContent = 'Selection Mode: OFF'; toggleBtn.style.background = '#e60023'; toggleBtn.style.color = '#fff'; toggleBtn.style.border = 'none'; toggleBtn.style.borderRadius = '8px'; toggleBtn.style.padding = '8px'; toggleBtn.style.cursor = 'pointer'; toggleBtn.addEventListener('click', () => { selectionMode = !selectionMode; toggleBtn.textContent = selectionMode ? 'Selection Mode: ON' : 'Selection Mode: OFF'; if (!selectionMode) clearSelections(); }); panel.appendChild(toggleBtn); const counter = document.createElement('div'); counter.id = 'selectedCounter'; counter.textContent = 'Selected: 0'; counter.style.textAlign = 'center'; counter.style.fontWeight = 'bold'; panel.appendChild(counter); const deleteBtn = document.createElement('button'); deleteBtn.textContent = '🗑️ Delete Selected'; deleteBtn.style.background = '#444'; deleteBtn.style.color = '#fff'; deleteBtn.style.border = 'none'; deleteBtn.style.borderRadius = '8px'; deleteBtn.style.padding = '8px'; deleteBtn.style.cursor = 'pointer'; deleteBtn.addEventListener('click', () => { if (selectedPins.size === 0) { alert("No pins selected."); return; } deletePinsAPI(); }); panel.appendChild(deleteBtn); document.body.appendChild(panel); } // ---- Update counter ---- function updateCounter() { const counter = document.getElementById('selectedCounter'); if (counter) counter.textContent = `Selected: ${selectedPins.size}`; } // ---- Toggle pin selection ---- function togglePinSelection(pinCard) { const pinId = pinCard.getAttribute("data-test-pin-id"); if (!pinId) return; if (selectedPins.has(pinId)) { selectedPins.delete(pinId); pinCard.style.outline = ""; } else { selectedPins.add(pinId); pinCard.style.outline = "4px solid red"; } updateCounter(); } // ---- Clear selections ---- function clearSelections() { document.querySelectorAll('[data-test-id="pin"]').forEach(el => el.style.outline = ""); selectedPins.clear(); updateCounter(); } // ---- Create progress bar ---- function createProgressBar(total) { let container = document.getElementById('deleteProgressContainer'); if (!container) { container = document.createElement('div'); container.id = 'deleteProgressContainer'; container.style.position = 'fixed'; container.style.top = '20px'; container.style.left = '50%'; container.style.transform = 'translateX(-50%)'; container.style.width = '300px'; container.style.background = '#fff'; container.style.border = '1px solid #ccc'; container.style.borderRadius = '12px'; container.style.boxShadow = '0 4px 12px rgba(0,0,0,0.15)'; container.style.padding = '10px'; container.style.zIndex = 10000; container.style.fontFamily = 'Arial, sans-serif'; container.style.display = 'flex'; container.style.flexDirection = 'column'; container.style.gap = '8px'; const text = document.createElement('div'); text.id = 'progressText'; text.style.textAlign = 'center'; text.style.fontWeight = 'bold'; container.appendChild(text); const barContainer = document.createElement('div'); barContainer.style.width = '100%'; barContainer.style.height = '16px'; barContainer.style.background = '#eee'; barContainer.style.borderRadius = '8px'; container.appendChild(barContainer); const bar = document.createElement('div'); bar.id = 'progressBar'; bar.style.width = '0%'; bar.style.height = '100%'; bar.style.background = '#e60023'; bar.style.borderRadius = '8px'; barContainer.appendChild(bar); document.body.appendChild(container); } document.getElementById('progressText').textContent = `Deleting 0 / ${total}`; document.getElementById('progressBar').style.width = '0%'; } function updateProgress(deleted, total) { const text = document.getElementById('progressText'); const bar = document.getElementById('progressBar'); if (text && bar) { text.textContent = `Deleting ${deleted} / ${total}`; bar.style.width = `${(deleted / total) * 100}%`; } } function hideProgress() { const container = document.getElementById('deleteProgressContainer'); if (container) container.remove(); } // ---- Delete pins via API ---- async function deletePinsAPI() { if (selectedPins.size === 0) return; const baseUrl = location.origin; const total = selectedPins.size; let deletedCount = 0; createProgressBar(total); for (const pinId of selectedPins) { try { const csrfToken = getCSRFToken(); if (!csrfToken) { alert("CSRF token not found. Refresh page and try again."); hideProgress(); return; } const payload = new URLSearchParams(); payload.append("source_url", location.pathname); payload.append("data", JSON.stringify({ options: { id: pinId }, context: {} })); const res = await fetch(`${baseUrl}/resource/PinResource/delete/`, { method: "POST", credentials: "include", headers: { "Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json, text/javascript, */*; q=0.01", "x-csrftoken": csrfToken }, body: payload }); if (res.ok) { deletedCount++; const el = document.querySelector(`[data-test-pin-id="${pinId}"]`); if (el) el.remove(); } else { console.warn(`Failed to delete ${pinId}`, await res.text()); } } catch (err) { console.error("Error:", err); } updateProgress(deletedCount, total); } hideProgress(); clearSelections(); location.reload(); // Reload page after deletion } // ---- Attach pin click listener ---- function attachPinClickListener() { document.body.addEventListener("click", (e) => { if (!selectionMode) return; const pinCard = e.target.closest('[data-test-id="pin"]'); if (pinCard) { e.preventDefault(); e.stopPropagation(); togglePinSelection(pinCard); } }, true); } // ---- Initialize panel ---- function initPanel() { if (!document.body) { setTimeout(initPanel, 500); return; } createPanel(); attachPinClickListener(); const observer = new MutationObserver(() => { if (!document.getElementById('massDeletePanel')) { createPanel(); } }); observer.observe(document.body, { childList: true, subtree: true }); } initPanel(); })();