您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Marks all visible "watched" films as "unwatched" from a list/grid view.
// ==UserScript== // @name Letterboxd Batch Mark as Unwatched // @namespace http://tampermonkey.net/ // @version 1.0 // @description Marks all visible "watched" films as "unwatched" from a list/grid view. // @author 0x00a // @match https://letterboxd.com/*/films/* // @match https://letterboxd.com/*/likes/films/* // @match https://letterboxd.com/*/diary/* // @grant GM_addStyle // @license MIT // ==/UserScript== (function() { 'use strict'; // --- Configuration --- const DELAY_BETWEEN_CLICKS = 500; // 0.5 second delay between each click. let isRunning = false; // --- Helper function for delays --- const sleep = ms => new Promise(resolve => setTimeout(resolve, ms)); // --- UI Control Panel --- function setupControlPanel() { if (document.getElementById('unmarker-panel')) return; const panelHtml = ` <div id="unmarker-panel"> <h3>Batch Mark as Unwatched</h3> <p style="font-size: 12px; color: #c25; font-weight: bold;">Important: Scroll down to load all films before starting!</p> <div id="unmarker-status">Status: Idle</div> <button id="start-unmarker-btn">Start Un-marking</button> <button id="stop-unmarker-btn">Stop</button> </div> `; GM_addStyle(` #unmarker-panel { position: fixed; top: 70px; right: 20px; z-index: 9999; background: #2C3440; color: #FFF; border: 1px solid #445; padding: 15px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.5); font-family: 'Letterboxd', 'Graphik', sans-serif; width: 250px; } #unmarker-panel h3 { margin-top: 0; color: #FFF; } #unmarker-panel button { margin-top: 10px; width: 100%; padding: 8px; cursor: pointer; background-color: #E9A100; color: white; border: none; border-radius: 4px; font-weight: bold; } #unmarker-panel #stop-unmarker-btn { background-color: #9A231F; } #unmarker-status { font-weight: bold; margin-bottom: 10px; padding: 5px; background-color: #1A1F26; border-radius: 3px;} `); document.body.insertAdjacentHTML('beforeend', panelHtml); document.getElementById('start-unmarker-btn').addEventListener('click', startProcess); document.getElementById('stop-unmarker-btn').addEventListener('click', stopProcess); } function updateStatus(message) { const statusEl = document.getElementById('unmarker-status'); if (statusEl) statusEl.textContent = `Status: ${message}`; } // --- Core Logic --- function stopProcess() { isRunning = false; console.log("Process stopped by user."); } async function startProcess() { if (isRunning) { alert("Process is already running."); return; } isRunning = true; // Find all "watched" films and then the "eye" icon within them. const watchedIcons = document.querySelectorAll('li.film-watched .icon-watched'); const totalToProcess = watchedIcons.length; if (totalToProcess === 0) { updateStatus("No watched films found on the page."); isRunning = false; return; } for (let i = 0; i < totalToProcess; i++) { if (!isRunning) { updateStatus(`Stopped by user at item ${i + 1}.`); break; } const icon = watchedIcons[i]; const filmContainer = icon.closest('li.poster-container'); const filmName = filmContainer.querySelector('img')?.alt || `Item ${i + 1}`; // Scroll the poster into view so we can see the action filmContainer.scrollIntoView({ behavior: 'auto', block: 'center' }); await sleep(100); // Wait briefly for scroll to finish updateStatus(`Un-marking "${filmName}" (${i + 1}/${totalToProcess})`); icon.click(); // The main delay between each action await sleep(DELAY_BETWEEN_CLICKS); } if (isRunning) { updateStatus(`Finished! Processed ${totalToProcess} films.`); } isRunning = false; } // --- Initialization --- window.addEventListener('load', () => { setupControlPanel(); }); })();