Created
October 26, 2025 22:40
-
-
Save StellarStoic/cb3a412306ee4eb57ebeec697adae6af to your computer and use it in GitHub Desktop.
Delete Google photos one by one programmatically
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
| // Infinite slow google photos Delete Loop which does the job. | |
| // Visit photos.google.com and open the first image full screen. | |
| // Paste this script in a DEV console. | |
| // When you hit enter, the script will start deleting photos one by one. | |
| // You can stop it with stopDeleteLoop() or simply refresh the page. | |
| // To slow down or speed up the deletion process, play with timing. (Current settings provided me good balance without errors) | |
| // Images are only deleted from Google photos and any duplicates on other devices should not be affected. | |
| // Use at your own risk and create a backup of your data first by visiting https://takeout.google.com/ | |
| (function() { | |
| let totalDeleted = 0; | |
| let isRunning = false; | |
| let currentState = 'LOOKING_FOR_DELETE'; // States: LOOKING_FOR_DELETE, SELECTING_DELETE_OPTION, COMPLETED_CYCLE | |
| const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms)); | |
| function getElementByXPath(xpath) { | |
| return document.evaluate(xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; | |
| } | |
| function safeClick(element) { | |
| if (!element) return false; | |
| try { | |
| element.click(); | |
| return true; | |
| } catch (e) { | |
| return false; | |
| } | |
| } | |
| // Alternative method to find "Current photo only" by text content | |
| function findCurrentPhotoOnlyOption() { | |
| // Method 1: Try by XPath first (your provided XPath) | |
| const xpathElement = getElementByXPath('/html/body/div[1]/div/c-wiz/div[4]/c-wiz/div[1]/div[2]/div/span/div/div[9]/div[31]/div/ul/li[2]'); | |
| if (xpathElement) return xpathElement; | |
| // Method 2: Search by text content | |
| const menuItems = document.querySelectorAll('li[role="menuitem"]'); | |
| for (let item of menuItems) { | |
| if (item.textContent && item.textContent.includes('Current photo only')) { | |
| return item; | |
| } | |
| } | |
| // Method 3: Search by class names from your HTML | |
| const elements = document.querySelectorAll('.aqdrmf-rymPhb-ibnC6b'); | |
| for (let element of elements) { | |
| if (element.textContent && element.textContent.includes('Current photo only')) { | |
| return element; | |
| } | |
| } | |
| return null; | |
| } | |
| // Function to check for Move to Bin button | |
| function findMoveToBinButton() { | |
| // Method 1: Your XPath | |
| const xpathElement = getElementByXPath('/html/body/div[2]/div/div[2]/div[2]/div/div[1]/div/div[2]/button[2]/span[4]'); | |
| if (xpathElement) return xpathElement; | |
| // Method 2: Search by text content | |
| const buttons = document.querySelectorAll('button'); | |
| for (let button of buttons) { | |
| if (button.textContent && button.textContent.includes('Move to bin')) { | |
| return button; | |
| } | |
| } | |
| return null; | |
| } | |
| async function deleteLoop() { | |
| if (isRunning) { | |
| console.log('Delete loop already running'); | |
| return; | |
| } | |
| isRunning = true; | |
| console.log('Starting enhanced delete loop with "Move to Bin" priority...'); | |
| console.log('Make sure the first photo is already open in the viewer!'); | |
| while (isRunning) { | |
| try { | |
| console.log(`\n--- Deleting Photo ${totalDeleted + 1} (State: ${currentState}) ---`); | |
| // ALWAYS CHECK FOR MOVE TO BIN FIRST (HIGHEST PRIORITY) | |
| const moveToBin = findMoveToBinButton(); | |
| if (moveToBin) { | |
| console.log('Clicking move to bin (HIGH PRIORITY)...'); | |
| safeClick(moveToBin); | |
| totalDeleted++; | |
| console.log(`SUCCESS! Deleted photo ${totalDeleted}`); | |
| currentState = 'LOOKING_FOR_DELETE'; | |
| await wait(3000); // Wait for deletion and next image to load | |
| continue; // Skip the rest of the loop and start fresh | |
| } | |
| switch (currentState) { | |
| case 'LOOKING_FOR_DELETE': | |
| // STEP 1: Look for Delete button (only if Move to Bin is not available) | |
| const deleteBtn = getElementByXPath('/html/body/div[1]/div/c-wiz/div[4]/c-wiz/div[1]/div[2]/div/span/div/div[9]/span/button/div'); | |
| if (deleteBtn) { | |
| console.log('Clicking delete button...'); | |
| safeClick(deleteBtn); | |
| currentState = 'SELECTING_DELETE_OPTION'; | |
| await wait(2000); // Wait for menu to appear | |
| } else { | |
| console.log('Delete button not found. Checking if delete options are available...'); | |
| // Check if delete options are already visible | |
| const currentPhotoOption = findCurrentPhotoOnlyOption(); | |
| if (currentPhotoOption) { | |
| console.log('Delete menu already open! Moving to option selection...'); | |
| currentState = 'SELECTING_DELETE_OPTION'; | |
| } else { | |
| console.log('Waiting for delete button...'); | |
| await wait(2000); | |
| } | |
| } | |
| break; | |
| case 'SELECTING_DELETE_OPTION': | |
| // STEP 2: Look for and click "Current photo only" option (only if Move to Bin is not available) | |
| const currentPhotoOption = findCurrentPhotoOnlyOption(); | |
| if (currentPhotoOption) { | |
| console.log('Clicking "Current photo only"...'); | |
| safeClick(currentPhotoOption); | |
| totalDeleted++; | |
| console.log(`SUCCESS! Deleted photo ${totalDeleted}`); | |
| currentState = 'LOOKING_FOR_DELETE'; | |
| await wait(3000); // Wait for next image to load | |
| } else { | |
| console.log('"Current photo only" option not found. Checking state...'); | |
| // If option not found, check if we're back to delete button (cycle completed) | |
| const deleteBtn = getElementByXPath('/html/body/div[1]/div/c-wiz/div[4]/c-wiz/div[1]/div[2]/div/span/div/div[9]/span/button/div'); | |
| if (deleteBtn) { | |
| console.log('Cycle completed! Photo was deleted. Moving to next...'); | |
| totalDeleted++; | |
| console.log(`SUCCESS! Deleted photo ${totalDeleted}`); | |
| currentState = 'LOOKING_FOR_DELETE'; | |
| } else { | |
| console.log('Waiting for "Current photo only" option...'); | |
| await wait(2000); | |
| } | |
| } | |
| break; | |
| } | |
| } catch (error) { | |
| console.log('Error in delete loop:', error); | |
| // Reset state on error to prevent infinite stuck state | |
| currentState = 'LOOKING_FOR_DELETE'; | |
| await wait(2000); | |
| } | |
| } | |
| } | |
| function stopDeleteLoop() { | |
| isRunning = false; | |
| console.log('Stopped delete loop'); | |
| console.log(`Total deleted: ${totalDeleted}`); | |
| } | |
| // Manual start function for testing | |
| function startDeleteLoop() { | |
| if (!isRunning) { | |
| deleteLoop(); | |
| } | |
| } | |
| // Auto-start the delete loop | |
| console.log('AUTO-STARTING ENHANCED DELETE LOOP IN 3 SECONDS...'); | |
| console.log('Make sure a photo is open in the viewer!'); | |
| setTimeout(() => { | |
| deleteLoop(); | |
| }, 3000); | |
| console.log(` | |
| To stop: Run stopDeleteLoop() or refresh the page | |
| Commands: | |
| - stopDeleteLoop() - Stop the loop | |
| - startDeleteLoop() - Start manually | |
| - totalDeleted - See count: ${totalDeleted} | |
| `); | |
| // Make functions globally available | |
| window.stopDeleteLoop = stopDeleteLoop; | |
| window.startDeleteLoop = startDeleteLoop; | |
| window.totalDeleted = totalDeleted; | |
| })(); |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Forget this, because this is so much better... https://github.com/mrishab/google-photos-delete-tool