Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save mtafasahin/247134d66b488c4306fd7ab2c19644dd to your computer and use it in GitHub Desktop.

Select an option

Save mtafasahin/247134d66b488c4306fd7ab2c19644dd to your computer and use it in GitHub Desktop.
Remove video shorts from YT Watch History
// 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