Skip to content

Instantly share code, notes, and snippets.

@mbarnes
Last active August 25, 2023 15:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mbarnes/15d8d15b4e17f5b559e6b8d0f21b17e0 to your computer and use it in GitHub Desktop.
Save mbarnes/15d8d15b4e17f5b559e6b8d0f21b17e0 to your computer and use it in GitHub Desktop.
Acme Fresh Market Digital Coupon Clipper
#!/usr/bin/python3
#
# Clip all available digital coupons for an Acme Fresh Market account.
#
# To avoid interactive prompts, either set environment variables
# ACME_STORES_USERNAME and ACME_STORES_PASSWORD or add credentials
# to you ~/.netrc file:
#
# machine acmestores.com
# login <ACME_STORES_USERNAME>
# password <ACME_STORES_PASSWORD>
#
import getpass
import html.parser
import http
import json
import netrc
import os
import re
import urllib
# 3rd-party modules
import requests
# Show HTTP requests and responses
http.client.HTTPConnection.debuglevel = 0
USER_AGENT = 'Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0'
VERIFICATION_TOKEN_COOKIE_NAME = '__RequestVerificationToken'
def acme_stores_session_login(method):
def inner(session, *args, **kwargs):
"""Log in to AcmeStores.com on first call"""
if VERIFICATION_TOKEN_COOKIE_NAME not in session.cookies:
url = '/account/login'
response = session.get(url)
response.raise_for_status()
token = session.cookies.get(VERIFICATION_TOKEN_COOKIE_NAME, path='/')
data = {
'Email': session.email,
'Password': session.password,
VERIFICATION_TOKEN_COOKIE_NAME: token
}
response = session.post(url, data=data)
response.raise_for_status()
return method(session, *args, **kwargs)
return inner
class AcmeStoresSession(requests.Session):
"""AcmeStores.com REST API session"""
base_url = 'https://www.acmestores.com'
def __init__(self, base_url=None):
if base_url:
self.base_url = base_url
super().__init__()
self.headers['User-Agent'] = USER_AGENT
self.__get_credentials()
self.cardno = None
def __get_credentials(self):
self.email = os.environ.get('ACME_STORES_USERNAME')
self.password = os.environ.get('ACME_STORES_PASSWORD')
if not (self.email and self.password):
try:
if auth := netrc.netrc().authenticators('acmestores.com'):
self.email, _, self.password = auth
except FileNotFoundError:
pass
if not (self.email and self.password):
print('Acme Fresh Market Login')
self.email = input('Email: ').strip()
self.password = getpass.getpass('Password: ').strip()
def request(self, method, url, *args, **kwargs):
"""Send the request after generating the complete URL"""
url = self.create_url(url)
return super().request(method, url, *args, **kwargs)
def create_url(self, url):
"""Create the URL based off this partial path"""
return urllib.parse.urljoin(self.base_url, url)
@acme_stores_session_login
def get_digital_deals(self):
"""List available digital deals"""
url = '/account/GetDigitalDeals'
response = self.get(url)
response.raise_for_status()
data = response.json()
self.cardno = data['cardNo']
yield from data['offers']
@acme_stores_session_login
def clip_digital_deal(self, offer):
"""Clip a digital deal"""
url = '/account/adddigitaldeal'
body = {
'cardNo': self.cardno,
'offer': offer['Id'],
'provider': offer['Provider'],
'isGold': False
}
response = self.post(url, json=body)
response.raise_for_status()
data = response.json()
return data['success']
def main():
with AcmeStoresSession() as session:
clipped = 0
for offer in session.get_digital_deals():
if offer['Clips'] == 0 and session.clip_digital_deal(offer):
print('CLIPPED:', offer['Description'].strip())
clipped += 1
print(clipped, 'coupons clipped')
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment