Skip to content

Instantly share code, notes, and snippets.

@qoomon
Last active April 15, 2024 07:25
Show Gist options
  • Save qoomon/a3f71ff2b2a18ee297c9435d539d5247 to your computer and use it in GitHub Desktop.
Save qoomon/a3f71ff2b2a18ee297c9435d539d5247 to your computer and use it in GitHub Desktop.
Clean YouTube Watch Later Videos
// Version 2.0.1
// This script will remove all videos from watch later list
//
// Usage
//
// #1 go to https://www.youtube.com/playlist?list=WL
// #2 run following script in your browser console
(async function() {
const playlistName = document.querySelector('.metadata-wrapper #container #text')?.textContent || document.querySelector('#text')?.textContent
if(!playlistName) {
alert(`Couldn't determine playlist name!`)
return
}
if(!confirm(`Are you sure to delete ALL videos from ${playlistName}?`)) {
return
}
console.info("start...")
while(true) {
const videos = document.querySelectorAll('#primary ytd-playlist-video-renderer')
if(videos.length == 0) break
for (let videoElement of videos) {
const videoTitle = videoElement.querySelector('a#video-title')
console.info(`Remove Video\n`
+ ` Title: ${videoTitle.innerText}\n`
+ ` URL: ${videoTitle.href}`)
const actionMenuButton = videoElement.querySelector('#menu #button')
console.debug("click actionMenuButton", actionMenuButton)
actionMenuButton.click()
const removeButton = await untilDefined(() => document.evaluate(
`//tp-yt-paper-listbox/ytd-menu-service-item-renderer[./tp-yt-paper-item/yt-formatted-string/span[text() = '${playlistName}']]`,
document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue)
console.debug("click removeButton", removeButton)
removeButton.click()
await sleep(200)
}
}
console.info("done!")
// === util functions ========================================================
async function sleep (timeout) {
return new Promise(res => setTimeout(res, timeout))
}
async function untilDefined(factory, checkInterval = 100) {
while (true) {
const value = factory()
if (value != null) return value
await sleep(checkInterval)
}
}
})();
@Mattia-Zanini
Copy link

Mattia-Zanini commented Mar 14, 2022

I'm an idiot, because i forgot to change REMOVE_BUTTON_TEXT, this code doesn't create any error, it's perfect, thx

@SteffeSteffe1988
Copy link

So i tried adding a line for Swedish:

// swe-SE: 'Ta bort från Titta senare'

But all I'm getting is "wait for removeButton VM229:48" a few thousand times over and over.

@qoomon
Copy link
Author

qoomon commented Mar 31, 2022

@SteffeSteffe1988 you need to replace following line

  • const REMOVE_BUTTON_TEXT = 'Remove from Watch later' // en with
    with
  • const REMOVE_BUTTON_TEXT = 'Ta bort från Titta senare' // swe-SE

@SteffeSteffe1988
Copy link

@SteffeSteffe1988 you need to replace following line

  • const REMOVE_BUTTON_TEXT = 'Remove from Watch later' // en with
    with
  • const REMOVE_BUTTON_TEXT = 'Ta bort från Titta senare' // swe-SE

Of course I missed something that obvious.
Thank you! Now it works great for people using Sweish in Chrome!

@qoomon
Copy link
Author

qoomon commented Mar 31, 2022

@SteffeSteffe1988 Glad to hear that, I'll add Swedish values as coment

@udovichenko
Copy link

Thank you so much, still works in 2023!
This is my fork with prompting the limit of videos to be deleted and randomized delay:
https://gist.github.com/udovichenko/295357dc6b504e4ce3dd540ad6c368a4

@othyn
Copy link

othyn commented Apr 28, 2023

YouTube appear to have changed the title document selector for me, so the script breaks when trying to locate the playlist title on line 16:

const playlistName = document.querySelector("#title a").text

Looking at the latest commits in sandorex's fork we can see the new selector that needs to be used as YouTube have changed things around.

Replacing it with something like the following should work, so replace this:

  const playlistName = document.querySelector("#title a").text

  if(!confirm(`Are you sure to delete ALL videos from ${playlistName}?`)) {
    return
  }

with this:

  let playlistName = document.querySelector('.metadata-wrapper #container #text')?.textContent || null;

  let confirmationText = 'Are you sure to delete ALL videos from ' + (!playlistName ? 'this playlist' : playlistName) + '?';

  if (!confirm(confirmationText)) {
    return
  }

I still don't get why they don't add this functionality into the native YouTube experience, its been years and it must be a really common issue once playlists fill up.

@qoomon
Copy link
Author

qoomon commented Apr 28, 2023

thx @othyn, I fixed it

@sandorex
Copy link

sandorex commented May 1, 2023

I've been testing my fork for few months that does not require lang specific options and modifications, could use more people trying it, also it's a Violentmonkey script
https://gist.github.com/sandorex/6bdd51ef467a079c87f19ba469a8bc7c
Feel free to take the code if you want

@qoomon
Copy link
Author

qoomon commented May 2, 2023

@sandorex thanks a lot, I've updated my script to your provided solution to get rid of language specific configuration.

@sandorex
Copy link

sandorex commented Aug 8, 2023

It seems youtube did something so the script broke a bit, change

const playlistName = document.querySelector('.metadata-wrapper #container #text')?.textContent

to

const playlistName = document.querySelector('#text')?.textContent

The id stayed the same for a while now so im hopeful it wont need more fixing for a while 😄

@qoomon
Copy link
Author

qoomon commented Aug 9, 2023

@sandorex thx a lot, however for me it' works as expected with document.querySelector('.metadata-wrapper #container #text')?.textContent

@sandorex
Copy link

Weird it works for me now too, it did not work yesterday for some reason

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment