Last active
August 25, 2023 15:15
-
-
Save mbarnes/15d8d15b4e17f5b559e6b8d0f21b17e0 to your computer and use it in GitHub Desktop.
Acme Fresh Market Digital Coupon Clipper
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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