Skip to content

Instantly share code, notes, and snippets.

@snacsnoc
Created May 24, 2025 16:22
Show Gist options
  • Select an option

  • Save snacsnoc/c1982e57ec720a66d87c8391a29b38dd to your computer and use it in GitHub Desktop.

Select an option

Save snacsnoc/c1982e57ec720a66d87c8391a29b38dd to your computer and use it in GitHub Desktop.
Load Dollar General coupons from file (JSON) and apply to account
#!/usr/bin/env python3
# load_dg_coupons.py
import argparse, json, sys, textwrap
from pathlib import Path
from urllib.parse import urlencode
import requests
# ---------- constants you normally NEVER edit in-file ----------
BASE = "https://www.dollargeneral.com/bin/omni/coupons"
ACTIV = f"{BASE}/activate"
HEAD = {
"User-Agent": "Mozilla/5.0",
"Accept": "application/json, text/javascript, */*; q=0.01",
"X-Requested-With": "XMLHttpRequest",
"Referer": "https://www.dollargeneral.com/deals/coupons",
"X-CSRF-Token": "NEEDTHIS", #enter your own
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
"Cookie": (
"form_key=6MzGRJsdB2ZZXMXA; " # probably need this
"c_uuid=2501015138020100101138051050168030; " # probably need this
"authType=1; " #probably need this too
),
}
BATCH = 20 # offers per POST
# -----------------------------------------------------------------
def debug(msg, *, on=True):
if on:
print(msg, file=sys.stderr)
def extract_ids(obj):
"""
Accepts either a dict that contains 'Coupons': […] or a plain
list of strings, returns list[str].
"""
if isinstance(obj, list):
return [str(x) for x in obj]
if isinstance(obj, dict) and "Coupons" in obj:
return [c["OfferID"] for c in obj["Coupons"] if "OfferID" in c]
raise ValueError("Unrecognised JSON structure")
def activate(sess, offer_ids, store, device, mobile=False):
data = {
"offerGuids": ",".join(offer_ids),
"storeNumber": store,
"deviceId": device,
"isMobileDevice": str(mobile).lower(),
"clientOriginStoreNumber": "",
}
r = sess.post(ACTIV, headers=HEAD, data=urlencode(data), timeout=20)
if r.status_code != 200:
sys.stderr.write(
f"\n--- HTTP {r.status_code} ---\n{r.text[:500]}\n-------------\n"
)
r.raise_for_status()
return r.json()
# -----------------------------------------------------------------
def main():
ap = argparse.ArgumentParser(
formatter_class=argparse.RawDescriptionHelpFormatter,
description="Load multiple Dollar General coupons from a JSON file.",
epilog=textwrap.dedent("""\
The file can be:
• the raw /search response (object with a 'Coupons' key), **or**
• a simple JSON list of OfferID strings.
"""),
)
ap.add_argument("-f", "--file", default="coupons.json",
help="JSON input file (default: coupons.json)")
ap.add_argument("--store", required=True, help="Store number")
ap.add_argument("--device", required=True, help="deviceId (same one in your cookies)")
ap.add_argument("-n", "--dry-run", action="store_true",
help="Show what would be sent but do NOT POST")
args = ap.parse_args()
j = json.loads(Path(args.file).read_text())
offers = extract_ids(j)
if not offers:
sys.exit("No OfferID values found – aborting")
debug(f"Found {len(offers)} coupons in {args.file}")
sess = requests.Session()
for i in range(0, len(offers), BATCH):
chunk = offers[i:i+BATCH]
debug(f"→ Activating {len(chunk)} offers [{i}..{i+len(chunk)-1}]")
if args.dry_run:
debug(" DRY-RUN " + ",".join(chunk))
continue
resp = activate(sess, chunk, args.store, args.device)
for item in resp.get("result", []):
ok = item.get("Success")
cid = item.get("OfferId")
note = "✓" if ok else f"✗ ({item.get('ErrorCode')})"
print(f"{note} {cid}")
if args.dry_run:
print("-- dry-run completed, nothing sent --")
# -----------------------------------------------------------------
if __name__ == "__main__":
main()
'''
Sample JSON
{
"Coupons": [
{
"DiscountIndicator": 0,
"UPCs": [],
"OfferID": "69766dbc-d23d-43f8-9584-470cc349497b",
"Image1": "https://cdn.cpnscdn.com/static.coupons.com/ext/bussys/cpa/pod/86/241/5286241_a822edc1-790c-4a0a-a7e4-1eaa72857d30.gif",
"Image2": "https://cdn.cpnscdn.com/static.coupons.com/ext/bussys/cpa/pod/86/241/5286241_a822edc1-790c-4a0a-a7e4-1eaa72857d30_high.jpg",
"OfferType": "AmountOff",
"RecemptionFrequency": "OnceTimeOffer",
"OfferGS1": "81101005280005980133001105003250614",
"OfferCode": "23014133",
"OfferActivationDate": "2025-05-03T00:01:00",
"OfferExpirationDate": "2025-06-14T23:59:59",
"OfferDescription": "on any ONE (1) LUBRIDERM® Product, any variety (16 fl. oz. or larger)",
"BrandName": "LUBRIDERM®",
"Companyname": "KENVUE BRANDS LLC.-SKILLMAN",
"OfferSummary": "Save $3.00",
"OfferFinePrint": "",
"OfferDisclaimer": "CONSUMER: This coupon good only on purchase of product indicated. Any other use constitutes fraud. COUPON CANNOT BE BOUGHT, TRANSFERRED OR SOLD. LIMIT ONE COUPON PER PURCHASE AND ONE COUPON PER CUSTOMER. VOID IF TAXED, RESTRICTED OR PROHIBITED BY LAW. VOID IF COPIED OR EXPIRED.\n\nRETAILER: Kenvue Brands LLC. will reimburse you for the face value of this coupon plus 8¢ handling if submitted in compliance with Kenvue Brands LLC. Coupon Redemption Policy. Coupons are void where prohibited, taxed, or otherwise restricted by law. Cash Redemption Value 1/100 cent. Send coupons to: Kenvue Brands LLC. 1453, NCH, P.O. Box 880001, El Paso, TX 88588-0001.\n\n© Kenvue Brands LLC 2025",
"OfferFeaturedText": "",
"IsAutoActivated": 0,
"TargetType": "recommended",
"Visible": "",
"ActivationDate": "0001-01-01T00:00:00",
"AssociationCode": "",
"RedemptionLimitQuantity": 1,
"MinBasketValue": 0.0,
"MinTripCount": 0,
"TimesShopQuantity": 0,
"MinQuantity": 1,
"MinQuantityDescription": "",
"RewaredOfferValue": 3.0,
"RewaredCategoryName": "Personal Care",
"RewardQuantity": 0,
"IsClipped": 0,
"IsManufacturerCoupon": 1,
"offerSourceType": 1,
"isProductEligible": true,
"CouponType": 1,
"IsBasketLevel": false
},
}
'''
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment