Skip to content

Instantly share code, notes, and snippets.

@raxityo
Last active March 1, 2025 15:26
Show Gist options
  • Save raxityo/c82abe49b77d5feaeb5cf6b69f0e0d9d to your computer and use it in GitHub Desktop.
Save raxityo/c82abe49b77d5feaeb5cf6b69f0e0d9d to your computer and use it in GitHub Desktop.
Clip all available BJ's coupons
// ==UserScript==
// @name BJ's Coupon Clipper
// @namespace Violentmonkey Scripts
// @match https://www.bjs.com/
// @grant none
// @version 1.0
// @author @raxityo
// @description 3/24/2024, 11:01:30 PM
// ==/UserScript==
/**
* Clip all available BJ's coupons
* - Login to BJ's website
* - Open dev console and paste this function
* - Run it! Example `await clipAllOffers()`
* @return {Promise<T>}
*/
(async function clipAllOffers() {
const membershipNumber = localStorage.getItem("x_MembershipNumber");
const zipcode = JSON.parse(
localStorage.getItem("clubDetailsForClubId")
).postalCode;
await fetch(
"https://api.bjs.com/digital/live/api/v1.0/member/available/offers",
{
method: "post",
credentials: "include",
body: JSON.stringify({
membershipNumber,
zipcode,
category: "",
isPrev: false,
isNext: true,
pagesize: 500,
searchString: "",
indexForPagination: 0,
brand: ""
})
}
)
.then((r) => r.json())
.then(([{ availableOffers }]) => {
// Intentionally doing sequential requests to avoid hammering the backend
availableOffers.forEach(async ({ offerId, storeId }) => {
await fetch(
`https://api.bjs.com/digital/live/api/v1.0/store/${storeId}/coupons/activate?zip=07302&offerId=${offerId}`,
{
credentials: "include"
}
);
});
});
})().then();
@TheMarf
Copy link

TheMarf commented Mar 23, 2024

Love this code, was able to get it to work like a charm. Not very versed in js, is there any easy way to save this to a bookmark or something to easily run in firefox?

Edit: tried making a bookmark with the following...don't think it worked. Will try again in a couple days when coupons refresh.

javascript:(async function clipAllOffers() { const membershipNumber = localStorage.getItem('x_MembershipNumber') const zipcode = JSON.parse(localStorage.getItem('clubDetailsForClubId')).postalCode await fetch('https://api.bjs.com/digital/live/api/v1.0/member/available/offers', { method: 'post', credentials: 'include', body: JSON.stringify({ membershipNumber, zipcode, 'category': '', 'isPrev': false, 'isNext': true, 'pagesize': 500, 'searchString': '', 'indexForPagination': 0, 'brand': '' }) }) .then(r => r.json()) .then(([{ availableOffers }]) => { // Intentionally doing sequential requests to avoid hammering the backend availableOffers.forEach(async ({ offerId, storeId }) => { await fetch( https://api.bjs.com/digital/live/api/v1.0/store/${storeId}/coupons/activate?zip=07302&offerId=${offerId}`, { credentials: 'include' } ) }) }) })().then()`

@raxityo
Copy link
Author

raxityo commented Mar 25, 2024

@TheMarf I have just converted this to a UserScript, so you can use it in FireFox or other browsers using plugins such as Tampermonkey or ViolentMonkey and installing the script as a UeserScript. Doing so would automatically run the script whenever you visit bjs.com.

@TheMarf
Copy link

TheMarf commented Mar 27, 2024

@raxityo Great idea. Thanks for the tip and the code!

@vijankush
Copy link

vijankush commented Mar 1, 2025

I've done some improvements to the script, including adding a button on the page. Thank you for the script:

Button

// ==UserScript==
// @name         BJ's Coupon Clipper with Live Count
// @namespace    Violentmonkey Scripts
// @match        *://*.bjs.com/*
// @grant        none
// @version      1.5
// @author       @raxityo
// @description  Adds a button that shows a live count of coupons being clipped on any BJ's website subdomain.
// ==/UserScript==

(function() {
  'use strict';

  async function clipAllOffers() {
    button.disabled = true;
    button.style.backgroundColor = "#ccc";
    button.style.cursor = "not-allowed";

    try {
      const membershipNumber = localStorage.getItem("x_MembershipNumber");
      const clubDetails = localStorage.getItem("clubDetailsForClubId");
      if (!membershipNumber || !clubDetails) {
        console.error("Missing membership or club details in localStorage.");
        button.textContent = "Missing login/club info";
        return;
      }
      const zipcode = JSON.parse(clubDetails).postalCode;

      const response = await fetch("https://api.bjs.com/digital/live/api/v1.0/member/available/offers", {
        method: "POST",
        credentials: "include",
        body: JSON.stringify({
          membershipNumber,
          zipcode,
          category: "",
          isPrev: false,
          isNext: true,
          pagesize: 500,
          searchString: "",
          indexForPagination: 0,
          brand: ""
        })
      });
      const data = await response.json();

      if (!data || !Array.isArray(data) || !data[0]?.availableOffers) {
        console.error("Unexpected response structure.", data);
        button.textContent = "Unexpected response";
        return;
      }

      const offers = data[0].availableOffers;
      const total = offers.length;

      if (total === 0) {
        button.textContent = "No coupons available";
        return;
      }

      for (let i = 0; i < total; i++) {
        button.textContent = `Clipping coupon ${i + 1} of ${total}`;
        const { offerId, storeId } = offers[i];
        await fetch(
          `https://api.bjs.com/digital/live/api/v1.0/store/${storeId}/coupons/activate?zip=07302&offerId=${offerId}`,
          {
            credentials: "include"
          }
        );
      }

      button.textContent = "Coupons clipped successfully!";
    } catch (error) {
      console.error("Error clipping coupons:", error);
      button.textContent = "Error clipping coupons";
    }
  }

  const button = document.createElement("button");
  button.textContent = "Clip All Coupons";
  button.style.position = "fixed";
  button.style.top = "10px";
  button.style.right = "10px";
  button.style.zIndex = "10000";
  button.style.padding = "10px 15px";
  button.style.backgroundColor = "#007bff";
  button.style.color = "#fff";
  button.style.border = "none";
  button.style.borderRadius = "5px";
  button.style.cursor = "pointer";
  button.style.boxShadow = "0 2px 6px rgba(0,0,0,0.3)";

  button.addEventListener("click", clipAllOffers);

  function addButton() {
    if (!document.body.contains(button)) {
      document.body.appendChild(button);
    }
  }

  const observer = new MutationObserver(addButton);
  observer.observe(document.body, { childList: true, subtree: true });

  addButton();
})();

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