Last active
November 30, 2024 04:38
-
-
Save DinoChiesa/f3f2aa34d7d581782a0b8572fad99278 to your computer and use it in GitHub Desktop.
Remove video shorts from YT Watch History
This file contains 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
// orig: https://gist.github.com/miketromba/334282421c4784d7d9a191ca25095c09 | |
// Paste the script into your console on this page: https://myactivity.google.com/product/youtube | |
const ENABLED = true; | |
const MIN_DURATION_MS = 1000 * 60 * 1.5; // 1:30 mins | |
const CHECK_FOR_CONFIRM_INTERVAL = 2000; | |
let CYCLE_INTERVAL = 1800; // Amount of time in MS to wait between deletion (more likely to fail with a small number) | |
let wantCycling = true; | |
const VERY_LONG_DURATION = MIN_DURATION_MS * 10; | |
const alreadyRemoved = []; | |
const durationString= (vidElement) => { | |
const elt = vidElement.querySelector('[aria-label="Video duration"]'), | |
tc = elt && elt.textContent; | |
return tc && tc.trim(); | |
}; | |
const duration = (vidElement) => { | |
const dString = durationString(vidElement); | |
if ( ! dString) { | |
// unknown, something else is wrong | |
return VERY_LONG_DURATION; | |
} | |
if(dString.split(':').length > 2) { // The video is > 1hr long | |
return VERY_LONG_DURATION; | |
} | |
// less than an hour | |
let [mins, secs] = dString.split(':'); | |
if ( ! mins || !secs){ | |
return VERY_LONG_DURATION; | |
} | |
[mins, secs] = [mins, secs].map(stringNum => parseInt(stringNum, 10)); | |
const durationMS = (mins * 60 * 1000) + (secs * 1000); | |
return durationMS; | |
}; | |
const getIdentifiers = (videoElement) => | |
[...videoElement.getElementsByTagName('a')].map(anchor => anchor.textContent.trim()); | |
const vidUniquifier = (videoName, channelName) => `${videoName}|${channelName}`; | |
const isPreviouslyRemoved = (videoElement) => { | |
const [videoName, channelName] = getIdentifiers(videoElement); | |
return alreadyRemoved.includes(vidUniquifier(videoName, channelName)); | |
}; | |
const isShort = (videoElement) => { | |
try { | |
return duration(videoElement) < MIN_DURATION_MS; | |
} catch(e){ | |
console.log(`Exception while examining video: ${e}`); | |
return false; | |
} | |
} | |
async function deleteNext() { | |
// Extract next item from page | |
const nextItem = await getNextItem(); | |
if (nextItem) { | |
// Find name and author of video & log it to console | |
try { | |
const [videoName, channelName] = getIdentifiers(nextItem); | |
const dString = durationString(nextItem); | |
console.log(`deleteNext(): Will delete: ${videoName} by ${channelName} (${dString})...`); | |
// Find the next menu button for the item & click it | |
if (ENABLED) { | |
const deleteButton = nextItem.getElementsByTagName('button')[0]; | |
console.log(`deleteNext: DELETE: click...`); | |
deleteButton.click(); | |
alreadyRemoved.push(vidUniquifier(videoName, channelName)); | |
} | |
else { | |
console.log(`deleteNext: DELETE: NOT CLICKing...`); | |
} | |
} catch(e){ | |
console.log(`deleteNext(): while examining, exc: ${e}`); | |
} | |
} | |
else { | |
console.log(`deleteNext(): no item found.`); | |
} | |
if (wantCycling) { | |
// Wait [DELETE_INTERVAL] ms | |
setTimeout(() => { | |
// For the FIRST video, the YT UI pops up a dialog that the user must click through, | |
// to confirm the delete. Subsequent videos do not require this. | |
// Get the next delete button on the page & click it | |
const nextMenu = nextItem.querySelector('[aria-label="Activity options menu"]'); | |
if (nextMenu) { | |
const nextDeleteButton = nextMenu.querySelector('[aria-label="Delete activity item"]'); | |
if ( nextDeleteButton) { | |
nextDeleteButton.click(); | |
} | |
setTimeout(deleteNext, CYCLE_INTERVAL); | |
} | |
else { | |
// wait a bit, and look again | |
setTimeout(deleteNext, CYCLE_INTERVAL - CHECK_FOR_CONFIRM_INTERVAL); | |
} | |
}, CHECK_FOR_CONFIRM_INTERVAL); | |
} | |
} | |
function getNextItem() { | |
const items = document.querySelectorAll('div[role="listitem"]'); | |
console.log(`Found ${items.length} candidate items...`); | |
const nextItemToDelete = Array.from(items).find((v) => isShort(v) && !isPreviouslyRemoved(v)); | |
return nextItemToDelete; // possibly null | |
} | |
// This will start up the script | |
deleteNext() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Automate the selective deletion of items in your Youtube watch history
This script will enable you to automate the deletion selected videos from your YouTube watch history. The logic here will delete either ads, or shorts that run less than 1:30. If you are a coder, you can adjust the filter to select other items to delete.
There is no API, as far as I can tell, in the YT Data API, to allow deletion of items in the watch history. That part of the YT Data API was removed in 2016 or 2017. That means there is no "blessed" way of building a program to manage your own YT watch history. There may be a separate API published by Google for the "myactivity" page, which covers YT, web and app activity, location activity, and maybe more (not sure). But I didn't look into that. Lacking an official API, this script can help do what you might want.
To use this script
It should start to run immediately. It deletes one item in your watch history, every 4+ seconds or so.
Why would you want to do this?
If you have indulged in watching a long series of funny TikTok-style shorts, that can cause your YouTube feed to promote more shorts. If you want to avoid that, you may want to remove a bunch of your history. But not the long videos!
FAQs
Can you make it a Tampermonkey script? Yes - I did that here: https://gist.github.com/DinoChiesa/e33b13b81178dccc6e48cba01dea15d3 . You can use either approach.
Can you implement some other selection filters, like keyword filters? I could, but I'm leaving that as an exercise for you.
Why does it wait a few seconds between each delete? The YT UI inserts a user experience to notify the user that a delete is occurring, and even gives the user a chance to cancel the delete. This script waits a few seconds in order to allow that experience to occur.
The YT UI shows only 100 videos. Will the script need to be restarted after it deletes all shorts in the first 100 displayed? No. The page for YT is designed to reload when only a few videos are displayed. This brings more videos into the list, which this script can then scan as candidates for deletion.
Why did you make the script also delete ads? I don't know, I just didn't want the list of ads cluttering my watch history.
When I turn off "ENABLED" the script doesn't run properly. Why? I didn't test that part thoroughly. I didn't care too much about it, so I neglected it.
Inspired by and Derived from https://gist.github.com/miketromba/334282421c4784d7d9a191ca25095c09