Created
October 29, 2025 08:28
-
-
Save koenighotze/89e2a69b5cba093c8f297f1dc228a6c8 to your computer and use it in GitHub Desktop.
Remove videos from YT playlist
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| (async () => { | |
| // === CONFIG === | |
| const COUNT = 500; // how many from the top to remove | |
| const PER_ITEM_TIMEOUT = 8000; // ms to wait for menu items per video | |
| const BETWEEN_CLICKS_DELAY = 400; // ms delay between actions | |
| // === UTILITIES === | |
| const sleep = (ms) => new Promise(res => setTimeout(res, ms)); | |
| const waitFor = async (predicate, {timeout = 5000, interval = 100} = {}) => { | |
| const start = performance.now(); | |
| let result; | |
| while (performance.now() - start < timeout) { | |
| result = predicate(); | |
| if (result) return result; | |
| await sleep(interval); | |
| } | |
| return null; | |
| }; | |
| // Matchers for the "Remove from Watch later" menu item (EN + basic DE). | |
| const isRemoveItem = (el) => { | |
| const t = (el.textContent || '').trim().toLowerCase(); | |
| return ( | |
| t.includes('remove from watch later') || | |
| t.includes('aus "später ansehen" entfernen') || | |
| t.includes('aus „später ansehen“ entfernen') || // German curly quotes | |
| t.includes('aus später ansehen entfernen') | |
| ); | |
| }; | |
| // Ensure we are on the WL playlist page | |
| if (!/\/playlist\?/.test(location.pathname + location.search) || !/[\?&]list=WL\b/.test(location.search)) { | |
| console.warn('Please open https://www.youtube.com/playlist?list=WL and run again.'); | |
| return; | |
| } | |
| // Helper to (re)collect visible playlist items | |
| const getItems = () => Array.from(document.querySelectorAll('ytd-playlist-video-renderer')); | |
| // Scroll enough to load at least COUNT items | |
| const ensureItemsLoaded = async (count) => { | |
| let tries = 0; | |
| while (getItems().length < count && tries < 50) { | |
| window.scrollBy(0, 1200); | |
| await sleep(200); | |
| tries++; | |
| } | |
| }; | |
| await ensureItemsLoaded(COUNT); | |
| const items = getItems().slice(0, COUNT); | |
| if (items.length === 0) { | |
| console.warn('No videos found. Ensure the list is visible and not empty.'); | |
| return; | |
| } | |
| console.log(`Attempting to remove ${items.length} item(s) from the top of Watch later...`); | |
| let removed = 0, failed = 0; | |
| for (let idx = 0; idx < items.length; idx++) { | |
| const item = items[idx]; | |
| // Bring the item into view to help YouTube attach the proper menu | |
| item.scrollIntoView({block: 'center'}); | |
| await sleep(150); | |
| try { | |
| // 1) Open the per-item action menu | |
| // Most stable: the first button inside the #menu area of the item. | |
| let menuBtn = | |
| item.querySelector('#menu button') || | |
| item.querySelector('ytd-menu-renderer #button') || | |
| item.querySelector('button[aria-haspopup="true"]'); | |
| if (!menuBtn) { | |
| throw new Error('Menu button not found'); | |
| } | |
| menuBtn.click(); | |
| // 2) Wait for the menu panel and find the "Remove from Watch later" entry | |
| const menuItem = await waitFor(() => { | |
| // YouTube uses ytd-menu-service-item-renderer for items in the popup menu | |
| const candidates = Array.from(document.querySelectorAll('ytd-menu-service-item-renderer, tp-yt-paper-item, ytd-compact-link-renderer')); | |
| return candidates.find(isRemoveItem); | |
| }, { timeout: PER_ITEM_TIMEOUT }); | |
| if (!menuItem) { | |
| // Try closing any open menu so subsequent iterations are clean | |
| document.body.click(); | |
| throw new Error('Remove menu item not found (UI/locale may differ).'); | |
| } | |
| // 3) Click the remove action | |
| (menuItem.querySelector('a, tp-yt-paper-item, yt-formatted-string, button') || menuItem).click(); | |
| removed++; | |
| console.log(`Removed #${idx + 1} ✓`); | |
| await sleep(BETWEEN_CLICKS_DELAY); | |
| } catch (e) { | |
| failed++; | |
| console.warn(`Failed on item #${idx + 1}: ${e.message}`); | |
| // Try to close any stuck menu/snackbar | |
| document.body.click(); | |
| await sleep(200); | |
| } | |
| } | |
| console.log(`Done. Removed: ${removed}, Failed: ${failed}.`); | |
| // Optional: refresh the page to reflect changes immediately | |
| // location.reload(); | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment