Skip to content

Instantly share code, notes, and snippets.

@ctsstc
Last active April 20, 2025 19:08
Show Gist options
  • Save ctsstc/73a74ae0f0c315262bf07cea9fdc7aa2 to your computer and use it in GitHub Desktop.
Save ctsstc/73a74ae0f0c315262bf07cea9fdc7aa2 to your computer and use it in GitHub Desktop.
Safeway Just for U Auto Clicker. Add all the discounts at once.

Usage

To utilize this script you can create a bookmarklet from it utilizing some tool like:

You can also add it to your Chromium based browser via:

You can also just copy and paste this script into your browser's developer console as well.

For the more ambitious maybe you'll make a puppeteer script, or a browser extension 🔥

DTG Presentation & Additional Information

// Allow redefinition for copy/paste
let Clicker = class {
#document;
#selector;
#delay;
#randomWait;
#randomWaitMax;
constructor(
document,
selector,
{ delay = 1000, randomWait = true, randomWaitMax = 250 } = {}
) {
this.#document = document;
this.#selector = selector;
this.#delay = delay;
this.#randomWait = randomWait;
this.#randomWaitMax = randomWaitMax;
}
hasMore() {
return this.#findAll().length > 0;
}
clickAll() {
return new Promise((resolve) => {
const all = this.#findAll();
all.forEach((el, index) =>
setTimeout(() => {
const isLast = index == all.length - 1;
el.click();
if (isLast) {
resolve(null);
}
}, index * this.#delay + this.#getRandomWait())
);
});
}
#getRandomWait() {
return this.#randomWait
? Math.floor(Math.random() * this.#randomWaitMax)
: 0;
}
#findAll() {
return this.#document.querySelectorAll(this.#selector);
}
};
let coupons = new Clicker(window.document, "[id^=couponAddBtn]");
let loadButton = new Clicker(window.document, ".btn.load-more", {
delay: 2000,
randomWaitMax: 500,
});
// Anonymous singleton
new (class {
#coupons;
#loadButton;
constructor(coupons, loadButton) {
this.#coupons = coupons;
this.#loadButton = loadButton;
}
async run() {
// State 1
// Has coupon buttons -> Click all buttons
if (this.#coupons.hasMore()) {
// Note: console.log has been removed
console.warn("GOTTA CLICK'EM ALL!!!");
await this.#coupons.clickAll();
this.run();
}
// State 2
// Has load more -> Click load more
else if (this.#loadButton.hasMore()) {
console.warn("LOAD MOARRR!!!");
await this.#loadButton.clickAll();
this.run();
}
// State 3
// Has neither -> Finish
else {
console.warn("LE FINI! 🚀");
}
}
})(coupons, loadButton).run();
@ctsstc
Copy link
Author

ctsstc commented Aug 17, 2020

Clicking and length checks could be refactored out, buuuut smoller™ ¯\_(ツ)_/¯

@ctsstc
Copy link
Author

ctsstc commented Feb 12, 2021

Would be nice to auto scroll the page, and I should have a queue to process these rather than clicking all of them instantly; maybe some randomness to the time between clicks too.

@ctsstc
Copy link
Author

ctsstc commented Feb 24, 2025

Updated to utilize blocking asynchronous code so that clicking doesn't accidentally stack up or require math to try and avoid overlapping. This removes the pollingLength from the SafewayClicker, but moves a delay to the Clicker.
This also likely updates some selectors that may have been old or too generalized if I didn't already do that.

@ctsstc
Copy link
Author

ctsstc commented Apr 12, 2025

Seems they've added a check if the event is "trusted" or not, thus checking if it came from a user or script. This would have to pivot to an extension to bypass this. They also prevent the event propagation so that nobody else can capture the event. Since most browsers load event handlers as they're register there's no getting in front of this from the user script land of the console.

@Bigtuna00
Copy link

Bigtuna00 commented Apr 13, 2025

Just noticed my (super simple) bookmarklet stopped working as well (javascript:$("[id^=couponAddBtn]").trigger('click');). What a bummer, been using this technique for years. Thanks for the update.

@Bigtuna00
Copy link

Bigtuna00 commented Apr 13, 2025

I was thinking of going to AutoHotkey route but looks like they've added some throttling as well, even manually clicking the button results in an error after a few clicks:
image
EDIT: my bad, that came from clicking the same button too fast. If you go slow enough not to click the same button twice it works ok so AutoHotkey may be an option.

@Bigtuna00
Copy link

Bigtuna00 commented Apr 13, 2025

Here's a simple AutoHotkey script that just clicks the left mouse button once a second; toggle with F12:

#Requires AutoHotkey v2.0

F12:: {  ; F12 = Auto-click
 Static on := False
 If on := !on
      SetTimer(Click, 1000), Click()
 Else SetTimer(Click, 0)
}

Place your mouse over the first coupon button and toggle the script. Luckily for now the Safeway site scrolls all the coupons into that same spot as you click them.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment