diff --git a/lidlplus/api.py b/lidlplus/api.py index 6521d3b..9fda298 100644 --- a/lidlplus/api.py +++ b/lidlplus/api.py @@ -7,6 +7,7 @@ import logging import re from datetime import datetime, timedelta +from json import JSONDecodeError import requests @@ -16,6 +17,7 @@ LegalTermsException, MissingLogin, ) +from lidlplus.html_receipt import parse_html_receipt try: from getuseragent import UserAgent @@ -39,7 +41,7 @@ class LidlPlusApi: _CLIENT_ID = "LidlPlusNativeClient" _AUTH_API = "https://accounts.lidl.com" - _TICKET_API = "https://tickets.lidlplus.com/api/v2" + _TICKET_API = "https://tickets.lidlplus.com/api" _COUPONS_API = "https://coupons.lidlplus.com/api" _COUPONS_V1_API = "https://coupons.lidlplus.com/app/api/" _PROFILE_API = "https://profile.lidlplus.com/profile/api" @@ -257,7 +259,7 @@ def tickets(self, only_favorite=False): If set to False (the default), all tickets will be retrieved. :type onlyFavorite: bool """ - url = f"{self._TICKET_API}/{self._country}/tickets" + url = f"{self._TICKET_API}/v2/{self._country}/tickets" kwargs = {"headers": self._default_headers(), "timeout": self._TIMEOUT} ticket = requests.get(f"{url}?pageNumber=1&onlyFavorite={only_favorite}", **kwargs).json() tickets = ticket["tickets"] @@ -268,8 +270,16 @@ def tickets(self, only_favorite=False): def ticket(self, ticket_id): """Get full data of single ticket by 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() + url = f"{self._TICKET_API}/v2/{self._country}/tickets/{ticket_id}" + try: + return requests.get(url, **kwargs).json() + except JSONDecodeError: + url = f"{self._TICKET_API}/v3/{self._country}/tickets/{ticket_id}" + receipt_json = requests.get(url, **kwargs).json() + return parse_html_receipt( + date=receipt_json["date"], + html_receipt=receipt_json["htmlPrintedReceipt"], + ) def coupon_promotions_v1(self): """Get list of all coupons API V1""" diff --git a/lidlplus/html_receipt.py b/lidlplus/html_receipt.py new file mode 100644 index 0000000..b1ba943 --- /dev/null +++ b/lidlplus/html_receipt.py @@ -0,0 +1,61 @@ +from typing import Any + +import lxml.html as html + + +def parse_html_receipt(date: str, html_receipt: str) -> dict[str, Any]: + dom = html.document_fromstring(html_receipt) + + receipt = { + "date": date, + "itemsLine": [], + } + for node in dom.xpath(r".//span[starts-with(@id, 'purchase_list_line_')]"): + if "class" not in node.attrib: + if not node.text.endswith(" B"): + continue + + *name_parts, price = node.text[:-2].split() + receipt["itemsLine"].append( + { + "name": " ".join(name_parts), + "originalAmount": price, + "isWeight": True, + "discounts": [], + } + ) + elif node.attrib["class"] == "currency": + receipt["currency"] = {"code": node.text.strip(), "symbol": node.attrib["data-currency"]} + elif node.attrib["class"] == "article": + if node.text.startswith(" "): + continue + + quantity_text = node.get("data-art-quantity") + if quantity_text is None: + is_weight = False + quantity = 1 + elif "," in quantity_text: + is_weight = True + quantity = quantity_text + else: + is_weight = False + quantity = quantity_text + + receipt["itemsLine"].append( + { + "name": node.attrib["data-art-description"], + "currentUnitPrice": node.attrib["data-unit-price"], + "isWeight": is_weight, + "quantity": quantity, + "discounts": [], + } + ) + elif node.attrib["class"] == "discount": + discount = abs(parse_float(node.text.split()[-1])) + receipt["itemsLine"][-1]["discounts"].append({"amount": str(discount).replace(".", ",")}) + + return receipt + + +def parse_float(text: str) -> float: + return float(text.replace(",", ".")) diff --git a/requirements.txt b/requirements.txt index 3196094..589bf81 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,4 +3,5 @@ oic>=1.4.0 requests>=2.28.1 selenium-wire>=5.1.0 webdriver-manager>=3.8.5 -blinker==1.7.0 \ No newline at end of file +blinker==1.7.0 +lxml>=5.3.0