Google Maps like much of the web has devolved into an AI generated slurry.
Nowadays every review I leave gets a reply “from” the business which is clearly generated by a machine. All the photos are inevitably going in to train Gemini. And the level-up gamification was fun at first but led to nothing more than endless grinding to reach the next level.
Anyway, I’m out. Have been for a little while. But I’ve left a trail of photos and digital detritus I’d rather clean up.
Google doesn’t have a way to bulk-delete your stuff (for obvious if annoying reasons) so I thought I’d write a little script to do it for me.
The scripts
There’s two scripts, one for photos and one for reviews. They’re pretty naive and need to be restarted from time to time but they should be safe enough.
Huge disclaimer: these will probably get out of date at some point and may not work. You should read and understand the code before you do anything with it.
For photos, make sure you’re on the “Photos” tab of the My Contribution section (see above), then I ran the following in the console:
(async () => {
const sleep = (time) => new Promise(resolve => setTimeout(resolve,time));
const go = async () => {
// click the kebab
document.querySelector('button[jsaction*="pane.photo.actionMenu"]:not([aria-hidden="true"])').click();
await sleep(200);
// click the delete menu item
document.querySelector('#action-menu div[role="menuitemradio"]').click();
await sleep(200);
// click the "yes I'm sure" button
document.querySelector('div[aria-label="Delete this photo?"] button+button,div[aria-label="Delete this video?"] button+button').click();
await sleep(1500);
// check if there's any left, and do it all again
if(document.querySelector('button[jsaction*="pane.photo.actionMenu"]:not([aria-hidden="true"])')) go();
}
go();
})()
And for reviews on the reviews tab:
// delete reviews from Google Maps
(async () => {
const sleep = (time) => new Promise(resolve => setTimeout(resolve,time));
const go = async () => {
// click the kebab
document.querySelector('button[jsaction*="review.actionMenu"]:not([aria-hidden="true"])').click();
await sleep(300);
// find the delete review menu item
const deleteMenuItem = document.querySelector('#action-menu div[role="menuitemradio"]+div');
if(deleteMenuItem.textContent.trim() !== 'Delete review') return console.error('wrong menu item', deleteMenuItem.textContent);
deleteMenuItem.click();
await sleep(300);
// click the "yes I'm sure" button
document.querySelector('div[aria-label="Delete this review?"] button+button').click();
await sleep(2000);
// check if there's any left, and do it all again
if(document.querySelector('button[jsaction*="review.actionMenu"]:not([aria-hidden="true"])')) go();
}
go();
})();
These are also on Github because my blog code formatting isn’t great. I should fix that up sometime.
What I learned hacking Google Maps (lol)
This code is pretty naive, and breaks a lot. I had to go back in a few times to restart the script, or adjust the timings when something broke. But it got there in the end.
I do appreciate the simplicity of the sleep()
function/promiseified setTimeout
. This isn’t in the language because it’s generally better to attach some kind of event or observer or do some polling to make sure the app is in the correct state. But in this case I thought it was a fairly elegant way to hack together a script in 5 minutes.
I could make this faster and more stable by implementing some kind of waitUntil(selector)
function to await the presence of the menu/dialog in the DOM. But I would also need a waitUntilNot(selector)
to wait for the deletion to finish. In any case that’s more complex, and we don’t need to overengineer this.
Anyway, the second script is done now and all my photos and reviews are gone. I’m still somehow a level 6 local guide (down from a level 7) so good for me.