Forked from DinoChiesa/delete-watch-history-shorts.console.js
Created
December 19, 2025 08:00
-
-
Save mtafasahin/247134d66b488c4306fd7ab2c19644dd to your computer and use it in GitHub Desktop.
Remove video shorts from YT Watch History
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
| // 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