Skip to content

Instantly share code, notes, and snippets.

@ctsstc
Last active March 15, 2025 10:08
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.

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