diff --git a/README.md b/README.md index 5e9dc97..28e0623 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/lidl-plus)](https://www.python.org/) [![PyPI - License](https://img.shields.io/pypi/l/lidl-plus)](https://github.com/Andre0512/lidl-plus/blob/main/LICENCE) [![PyPI - Downloads](https://img.shields.io/pypi/dm/lidl-plus)](https://pypistats.org/packages/lidl-plus) - + Fetch receipts and more from Lidl Plus. ## Installation ```bash @@ -50,10 +50,10 @@ lidl.login(phone="+4915784632296", password="password", verify_token_func=lambda print(lidl.refresh_token) ``` ## Usage -Currently, the only feature is fetching receipts +Currently, the only features are fetching receipts and activating coupons ### Receipts -Get your receipts as json and receive a list of bought items like: +Get your receipts as json and receive a list of bought items like: ```json { "currentUnitPrice": "2,19", @@ -89,6 +89,73 @@ for receipt in lidl.tickets(): pprint(lidl.ticket(receipt["id"])) ``` +### Coupons + +You can list all coupons and activate/deactivate them by id +```json +{ + "sections": [ + { + "name": "FavoriteStore", + "coupons": [] + }, + { + "name": "AllStores", + "coupons": [ + { + "id": "2c9b3554-a09c-412c-8be4-d41cbff13572", + "image": "https://lidlplusprod.blob.core.windows.net/images/coupons/LT/IDISC0000254911.png?t=1695452076", + "type": "Standard", + "offerTitle": "1 + 1", + "title": "👨🏻‍🍳 Frozen 👨🏻‍🍳", + "offerDescriptionShort": "FREE", + "isSegmented": false, + "startValidityDate": "2023-09-24T21:00:00Z", + "endValidityDate": "2023-10-01T20:59:59Z", + "isActivated": false, + "apologizeText": "Xxxxxxxxxxxxxxxxx", + "apologizeStatus": false, + "apologizeTitle": "Xxxxxxxxxxxxxxxxxxx", + "promotionId": "DISC0000254911", + "tagSpecial": "", + "firstColor": "#ffc700", + "secondaryColor": null, + "firstFontColor": "#4a4a4a", + "secondaryFontColor": null, + "isSpecial": false, + "hasAsterisk": false, + "isHappyHour": false, + "stores": [] + }, + ....... + ] + }, + { + "name": "OtherStores", + "coupons": [] + } + ] +} +``` + +#### Commandline-Tool + +Activate all available coupons + +```bash +$ lidl-plus --language=de --country=AT --refresh-token=XXXXX coupon --all +``` + +#### Python +```python +from lidlplus import LidlPlusApi + +lidl = LidlPlusApi("de", "AT", refresh_token="XXXXXXXXXX") +for section in lidl.coupons()["sections"]: + for coupon in section["coupons"]: + print("found coupon: ", coupon["title"], coupon["id"]) +``` + ## Help #### Commandline-Tool ```commandline @@ -110,4 +177,5 @@ options: commands: auth authenticate and get token receipt output last receipts as json + coupon activate coupons ``` diff --git a/lidlplus/__main__.py b/lidlplus/__main__.py index 4db7323..f29d703 100755 --- a/lidlplus/__main__.py +++ b/lidlplus/__main__.py @@ -8,6 +8,7 @@ import sys from getpass import getpass from pathlib import Path +from datetime import datetime if __name__ == "__main__": sys.path.insert(0, str(Path(__file__).parent.parent)) @@ -47,6 +48,9 @@ def get_arguments(): receipt = subparser.add_parser("receipt", help="output last receipts as json") receipt.add_argument("receipt", help="output last receipts as json", action="store_true") receipt.add_argument("-a", "--all", help="fetch all receipts", action="store_true") + coupon = subparser.add_parser("coupon", help="activate coupons") + coupon.add_argument("coupon", help="activate coupons", action="store_true") + coupon.add_argument("-a", "--all", help="activate all coupons", action="store_true", required=True) return vars(parser.parse_args()) @@ -120,6 +124,26 @@ def print_tickets(args): print(json.dumps(tickets, indent=4)) +def activate_coupons(args): + """Activate all available coupons""" + lidl_plus = lidl_plus_login(args) + if not args.get("all"): + return + i = 0 + for section in lidl_plus.coupons().get("sections", {}): + for coupon in section.get("coupons", {}): + if coupon["isActivated"]: + continue + if datetime.fromisoformat(coupon["startValidityDate"]) > datetime.now(): + continue + if datetime.fromisoformat(coupon["endValidityDate"]) < datetime.now(): + continue + print("activating coupon: ", coupon["title"]) + lidl_plus.activate_coupon(coupon["id"]) + i += 1 + print(f"Activated {i} coupons") + + def main(): """argument commands""" args = get_arguments() @@ -127,6 +151,8 @@ def main(): print_refresh_token(args) elif args.get("receipt"): print_tickets(args) + elif args.get("coupon"): + activate_coupons(args) def start(): diff --git a/lidlplus/api.py b/lidlplus/api.py index 44f25e0..be7a5d3 100644 --- a/lidlplus/api.py +++ b/lidlplus/api.py @@ -6,6 +6,7 @@ import logging import re from datetime import datetime, timedelta +from typing import Literal import requests @@ -38,6 +39,7 @@ class LidlPlusApi: _CLIENT_ID = "LidlPlusNativeClient" _AUTH_API = "https://accounts.lidl.com" _TICKET_API = "https://tickets.lidlplus.com/api/v2" + _COUPONS_API = "https://coupons.lidlplus.com/api" _APP = "com.lidlplus.app" _OS = "iOs" _TIMEOUT = 10 @@ -198,6 +200,16 @@ def _check_2fa_auth(self, browser, wait, verify_mode="phone", verify_token_func= browser.find_element(By.NAME, "VerificationCode").send_keys(verify_code) self._click(browser, (By.CLASS_NAME, "role_next")) + def _switch_coupon_activation(self, coupon_id: str, action: Literal["activate", "deactivate"]): + url = f"{self._COUPONS_API}/v1/{self._country}/{coupon_id}/activation" + kwargs = {"headers": self._default_headers(), "timeout": self._TIMEOUT} + if action == "activate": + return requests.post(url, **kwargs).json() + elif action == "deactivate": + return requests.delete(url, **kwargs).json() + else: + raise ValueError(f'Unknown action "{action}" - Only "activate" or "deactivate" supported') + def login(self, phone, password, **kwargs): """Simulate app auth""" browser = self._get_browser(headless=kwargs.get("headless", True)) @@ -252,3 +264,17 @@ def ticket(self, ticket_id): kwargs = {"headers": self._default_headers(), "timeout": self._TIMEOUT} url = f"{self._TICKET_API}/{self._country}/tickets" return requests.get(f"{url}/{ticket_id}", **kwargs).json() + + def coupons(self): + """Get list of all coupons""" + url = f"{self._COUPONS_API}/v2/{self._country}" + kwargs = {"headers": self._default_headers(), "timeout": self._TIMEOUT} + return requests.get(url, **kwargs).json() + + def activate_coupon(self, coupon_id: str): + """Activate single coupon by id""" + return self._switch_coupon_activation(coupon_id, "activate") + + def deactivate_coupon(self, coupon_id: str): + """Deactivate single coupon by id""" + return self._switch_coupon_activation(coupon_id, "deactivate")