-
Star
(477)
You must be signed in to star a gist -
Fork
(78)
You must be signed in to fork a gist
-
-
Save astamicu/eb351ce10451f1a51b71a1287d36880f to your computer and use it in GitHub Desktop.
UPDATED 22.11.2022 | |
It's been two years since the last update, so here's the updated working script as per the comments below. | |
Thanks to [BryanHaley](https://gist.github.com/BryanHaley) for this. | |
```javascript | |
setInterval(function () { | |
video = document.getElementsByTagName('ytd-playlist-video-renderer')[0]; | |
video.querySelector('#primary button[aria-label="Action menu"]').click(); | |
var things = document.evaluate( | |
'//span[contains(text(),"Remove from")]', | |
document, | |
null, | |
XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, | |
null | |
); | |
for (var i = 0; i < things.snapshotLength; i++) | |
{ | |
things.snapshotItem(i).click(); | |
} | |
}, 500); | |
``` | |
Non-english users will need to change "Action menu" and "Remove from" to what YouTube uses for their localization. |
Not sure if somebody mentioned it already, but you can click on your profile icon and select "Change language" (select English) and then there is no need for localized versions of the script. Next time you open youtube you simply change the language back, if it doesn't happen automatically.
Thanks for the script :-) I set the seconds down to 200ms - that speeded it up for me.
I was looking for hours for the button to remove everything at once, but it seems that Google removed it.
Brazilian Portuguese version:
setInterval(function () {
video = document.getElementsByTagName('ytd-playlist-video-renderer')[0];
video.querySelector('#primary button[aria-label="Menu de ações"]').click();
var things = document.evaluate(
'//span[contains(text(),"Remover de")]',
document,
null,
XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
null
);
for (var i = 0; i < things.snapshotLength; i++)
{
things.snapshotItem(i).click();
}
}, 500);
Brazilian Portuguese version:
setInterval(function () { video = document.getElementsByTagName('ytd-playlist-video-renderer')[0]; video.querySelector('#primary button[aria-label="Menu de ações"]').click(); var things = document.evaluate( '//span[contains(text(),"Remover de")]', document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null ); for (var i = 0; i < things.snapshotLength; i++) { things.snapshotItem(i).click(); } }, 500);
👍
Thanks for the script :-) I set the seconds down to 200ms - that speeded it up for me. I was looking for hours for the button to remove everything at once, but it seems that Google removed it.
probably because the requests were timing out
Made my own userscript which doesn't rely on the language and allows you to delete with progress threshold: https://gist.github.com/js6pak/33bdefdefac09c387f55d08c5b9526fa
I modified this to also remove videos published over X days old, and those that are private or deleted. This currently relies on the language though (English). It could be updated to look for 'no_thumbnail' in the thumbnail source though
Do you happen to still have this code as this would be very helpful. I just noticed I have 4,800 Watched Later and heard if I hit 5k I can't add anymore. I also msg'd js6pak on his forked page. I'll just do Date Added ( Oldest ) to make it the easiest, if I delete ones I don't want then oh well. Thanks for your time.
Polska wersja:
setInterval(function () {
video = document.getElementsByTagName('ytd-playlist-video-renderer')[0];
video.querySelector('#primary button[aria-label="Menu czynności"]').click();
var things = document.evaluate(
'//span[contains(text(),"Usuń z")]',
document,
null,
XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
null
);
for (var i = 0; i < things.snapshotLength; i++)
{
things.snapshotItem(i).click();
}
}, 400);
Hi guys!
Just wanna share my userscript (inspired by @colejd's script) to helps you tidy up your YT playlists : YT-Playlist-Cleaner
Here are the key features :
-Customisable settings for deletion criteria
-Auto-scroll functionality to process large playlists
-Improved user interface with progress bar and status updates
-Pause and resume functionality
-Configurable delays between deletions to avoid rate limiting
Tbh, I received some helpful suggestions from Claude AI to optimize some parts of my script before uploading it online.
Let me know what you think!
I created a similar script that works by just clicking the 3 dots and then on the "Remove from playlist" button. By default, this script removes 100 videos, but that is also adjustable
This should work for all languages, so paste the script into your console and run.
YT might change their UI and you may have different screen resolution, so you may need to adjust the click x and y for each. You may also need to increase the delay if videos don't load fast enough (you can also run it multiple times).
function simulateClick(x, y) {
// What acually does the clicking
var event = new MouseEvent('click', {
view: window,
bubbles: true,
cancelable: true,
clientX: x,
clientY: y
});
document.elementFromPoint(x, y).dispatchEvent(event);
}
function performClicks() {
let count = 0;
function clickSequence() {
// change this number to remove more videos
if (count >= 100) {
console.log("Completed 100 click sequences.");
return;
}
// change this when the UI changes, this should be the location of the 3 dots
simulateClick(1872, 256);
console.log(`Click sequence ${count + 1} pt 1`);
setTimeout(function() {
// change this when the UI changes, this should be the location of the "Remove From Playlist" button
simulateClick(1784, 333);
console.log(`Click sequence ${count + 1} pt 2`);
count++;
// Wait 100ms, you may want to set this to more time to let the next video load
setTimeout(clickSequence, 100);
// Wait 100ms, you may want to set this to more time to let the next video load
}, 100);
}
// Start
clickSequence();
}
// Execute the click sequences
performClicks();
This code prints your mouse location to the console so you can tailor the script
document.addEventListener('mousemove', function(event) {
let x = event.clientX;
let y = event.clientY;
console.log(`Mouse position: X: ${x}, Y: ${y}`);
});
Universal version for all languages:
setInterval(function () {
video = document.getElementsByTagName('ytd-playlist-video-renderer')[0];
video.querySelector(`#primary button[aria-label="${window.ytcfg.msgs["VIDEO_ACTION_MENU"]}"]`).click();
var things = document.evaluate(
`//span[contains(text(),"${document.querySelector('meta[name="title"]')?.content}")]`,
document,
null,
XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
null
);
for (var i = 0; i < things.snapshotLength; i++)
{
things.snapshotItem(i).click();
}
}, 400);
window.ytcfg
This object returns an undefined object for me when I tried it in english.
To remove hidden videos. Click the dots in Watch Later playlist and select "Show unavailable videos" then run this version of the script:
setInterval(function () {
video = document.getElementsByTagName('ytd-playlist-video-renderer')[0];
video.querySelector('#primary button').click();
var things = document.evaluate(
'//span[contains(text(),"Remove from")]',
document,
null,
XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
null
);
for (var i = 0; i < things.snapshotLength; i++)
{
things.snapshotItem(i).click();
}
}, 1000);
@nightbasilisk That worked perfect for me, but the timeout seemed really long. So I added a little something extra to randomize the clicking, but shorten the average up a little bit.
const getRandomMillis = (min, max) => {
return Math.random() * (max - min) + min
}
setInterval(function () {
video = document.getElementsByTagName('ytd-playlist-video-renderer')[0];
video.querySelector('#primary button').click();
var things = document.evaluate(
'//span[contains(text(),"Remove from")]',
document,
null,
XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
null
);
for (var i = 0; i < things.snapshotLength; i++)
{
things.snapshotItem(i).click();
}
}, getRandomMillis(700, 1100));
No longer works for me. It deletes the first one, and then the menu gets stuck at the top-left corner.
Thanks @vinivosh
Here's Vietnamese version, including removing the hidden videos and clearing the interval after the playlist is empty
- Go to Youtube Watch Later playlist
Ctrl + Shift + I
to open chrome devtools -> Choose the Console tab- Type 'allow pasting' in the console
- Paste this into the console
var deleteInterval = setInterval(function () {
video = document.getElementsByTagName('ytd-playlist-video-renderer')[0];
if(!video) {
clearInterval(deleteInterval);
return;
}
video.querySelector('#primary button[aria-label="Menu tác vụ"]').click();
var things = document.evaluate(
'//span[contains(text(),"Xóa khỏi")]',
document,
null,
XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
null
);
for (var i = 0; i < things.snapshotLength; i++)
{
things.snapshotItem(i).click();
}
}, 1000);
- Paste this into the console to remove the hidden videos
// Show hidden videos
var btnListWL = document.querySelector('.metadata-buttons-wrapper.style-scope.ytd-playlist-header-renderer');
btnListWL.lastElementChild.lastElementChild.lastElementChild.click();
setTimeout(() => {
var showHiddenVideoLink = document.querySelector('a[href="/playlist?list=WL"]').click();
}, 500);
// Remove the hidden videos
setTimeout(() => {
var deleteHiddenVideosInterval = setInterval(function () {
video = document.getElementsByTagName('ytd-playlist-video-renderer')[0];
if(!video) {
clearInterval(deleteHiddenVideosInterval);
return;
}
video.querySelector('button').click();
var things = document.evaluate(
'//span[contains(text(),"Xóa khỏi")]',
document,
null,
XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
null
);
for (var i = 0; i < things.snapshotLength; i++)
{
things.snapshotItem(i).click();
}
}, 1000);
}, 1000);
I found myself wanting to keep a certain amount of videos (i.e. the last 1,000 added), so I sorted by oldest and set the script to stop removing videos once it got to that number. Based on secbug's most recent version.
const KEEP_NUM_VIDEOS = 1000;
const getRandomMillis = (min, max) => {
return Math.random() * (max - min) + min
}
setInterval(function () {
let metadataDiv = document.getElementsByClassName('metadata-stats');
let byline = metadataDiv[0].getElementsByClassName('byline-item');
let formattedStr = byline[0].getElementsByClassName('yt-formatted-string');
let numVideosStr = formattedStr[0].innerHTML;
numVideos = Number(numVideosStr.replace(',',''));
if (numVideos > KEEP_NUM_VIDEOS) {
video = document.getElementsByTagName('ytd-playlist-video-renderer')[0];
video.querySelector('#primary button').click();
var things = document.evaluate(
'//span[contains(text(),"Remove from")]',
document,
null,
XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
null
);
for (var i = 0; i < things.snapshotLength; i++)
{
things.snapshotItem(i).click();
}
}
}, getRandomMillis(700, 1100));
G'day everyone!
I’m stokedto announce a major update to my userscript "YT-Playlist-Cleaner", packed with some fantastic new features and improvements:
🌟 New Features: Welcome message, dark mode, smart retry, and usage stats.
🚀 Enhanced: Error handling, performance, and UI responsiveness.
🛠️ Added Functionality: Batch processing, config persistence, and a cleaner codebase.
🐛 Bug Fixes: Scrolling, deletion, and notification issues resolved.
This update makes the script more powerful and reliable than ever.
The script is fully open-source: feel free to fork it, tweak it, and make it your own!
Collaboration is what makes Github and coding so amazing, am I right?
Looking forward to hearing your thoughts!
Cheers!
For french/france/français
setInterval(function () {
video = document.getElementsByTagName('ytd-playlist-video-renderer')[0];
video.querySelector('#primary button[aria-label="Menu d\'actions"]').click();
var things = document.evaluate(
'//span[contains(text(),"Supprimer de")]',
document,
null,
XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
null
);
for (var i = 0; i < things.snapshotLength; i++)
{
things.snapshotItem(i).click();
}
}, 500);
Thank you so much! I made 500 -> 200, faster. But after some deletion, it throws me a lot of error, that's why I'm using another browser (during the cooldown for errors :D)
Universal version for all languages:
setInterval(function () { video = document.getElementsByTagName('ytd-playlist-video-renderer')[0]; video.querySelector(`#primary button[aria-label="${window.ytcfg.msgs["VIDEO_ACTION_MENU"]}"]`).click(); var things = document.evaluate( `//span[contains(text(),"${document.querySelector('meta[name="title"]')?.content}")]`, document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null ); for (var i = 0; i < things.snapshotLength; i++) { things.snapshotItem(i).click(); } }, 400);
I used the universal script and it took me a while to realize that I have to start it and cloze the console to make it run. Works like a charm with my settings.
To remove hidden videos. Click the dots in Watch Later playlist and select "Show unavailable videos" then run this version of the script:
setInterval(function () { video = document.getElementsByTagName('ytd-playlist-video-renderer')[0]; video.querySelector('#primary button').click(); var things = document.evaluate( '//span[contains(text(),"Remove from")]', document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null ); for (var i = 0; i < things.snapshotLength; i++) { things.snapshotItem(i).click(); } }, 1000);
This one also works really well. It might be slow, but is running in the background now.
This script moves videos out of Watch later and into another playlist.
Just change the 'newPlaylistName' variable, and you're all set. 👍
// Move YouTube Videos From "Watch Later" Into A New Playlist.
setInterval(function () {
// define name of playlist to move videos into
const newPlaylistName = 'old watch later';
// 1) grab the first video entry
const video = document.querySelector('ytd-playlist-video-renderer');
if (!video) return;
// helper to open that video's action menu
function openMenu(cb) {
const btn = video.querySelector('ytd-menu-renderer yt-icon-button');
if (!btn) return console.warn('No action button');
btn.click();
setTimeout(cb, 400);
}
// 2) open menu & click “Save to…”
openMenu(() => {
const items = Array.from(document.querySelectorAll('ytd-menu-service-item-renderer'));
const saveItem = items.find(el =>
el.innerText.trim().toLowerCase().startsWith('save to')
);
if (!saveItem) {
console.warn('“Save to…” not found:', items.map(el => el.innerText));
return;
}
saveItem.click();
// 3) wait for dialog, click to add to new specified playlist
setTimeout(() => {
const option = Array.from(document.querySelectorAll('yt-formatted-string'))
.find(el => el.innerText.trim().toLowerCase() === newPlaylistName);
if (!option) {
console.warn('Playlist “' + newPlaylistName + '” not found');
} else {
option.click();
}
// 4) close save dialog
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' }));
// 5) after a short pause, re-open menu & click “Remove from Watch later”
setTimeout(() => {
openMenu(() => {
const remItems = Array.from(document.querySelectorAll('ytd-menu-service-item-renderer'));
const removeItem = remItems.find(el =>
el.innerText.trim().toLowerCase().includes('remove from watch later')
);
if (!removeItem) {
console.warn('“Remove from Watch later” not found:', remItems.map(el => el.innerText));
} else {
removeItem.click();
}
});
}, 500);
}, 500);
});
}, 500);
this one worked for me for the liked videos, not the private/hidden ones:
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function deleteLikedVideos() {
`use strict`;
var items = document.querySelectorAll(
`#primary ytd-playlist-video-renderer yt-icon-button.dropdown-trigger > button[aria-label]`
);
var out;
for (var i = 0; i < items.length; i++) {
items[i].click();
out = setTimeout(function () {
if (
document.querySelector(
`tp-yt-paper-listbox.style-scope.ytd-menu-popup-renderer`
).lastElementChild
) {
document
.querySelector(
`tp-yt-paper-listbox.style-scope.ytd-menu-popup-renderer`
)
.lastElementChild.click();
}
}, 100);
await sleep(500); // sleep cause browser can not handle the process
clearTimeout(out);
}
}
deleteLikedVideos();
I made an attempt before looking at the solutions here. For posterity's sake, here is my basic solution:
setInterval(function () {
document.getElementsByTagName("ytd-playlist-video-renderer")[0].querySelector("button").click();
setTimeout((() => document.querySelectorAll("ytd-menu-service-item-renderer")[2].click()), 500)
}, 1800);
I changed the array access to only delete old stuff, so it would make more sense to use document.getElementByTagName
if you're deleting everything, but I might as well keep it for later modification.
Where exactly is everyone posting this script onto? I know its somewhere within the the F12 (inspection) area of the browser but where exactly? Pictures would be greatly appreciated
Where exactly is everyone posting this script onto? I know its somewhere within the the F12 (inspection) area of the browser but where exactly? Pictures would be greatly appreciated
You have a tab called console in the development tools: https://developer.chrome.com/docs/devtools/console/javascript/
for the first time you might have to follow steps outlined in the console in order to paste a command
@LosAlamosAl if I had to guess, it's YouTube rate-limiting at work. Especially if it's a nice round number like 100.