Source code for cata_log_hub.providers.lidl
# SPDX-License-Identifier: AGPL-3.0-or-later
#
# Cata-Log - the central hub for digital flyers
# Copyright (C) 2026 David Aderbauer & The Cata-Log Contributors
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from datetime import datetime, timedelta
from typing import override
from pydantic import Field
from cata_log_hub.exceptions import CatalogUnavailableWarning, PagesExhausted
from cata_log_hub.utils.page_numbers import PageNumber
from .base import Preview, Provider
from .regions import Germany, Italy
[docs]
class LidlDeutschland(Provider):
"""Provider class for the current german Lidl catalog."""
uid = "lidl-de"
name = "Lidl"
description = "Lidl Angebote"
url = "https://www.lidl.de/c/online-prospekte/s10005610"
region = Germany
first_page_number = 0
[docs]
class Configuration(Provider.Configuration):
region_id: str = Field(
default="0",
description="""ID der Lidl Region.
Öffne lidl.de und wähle deine Filiale.
Öffne den Web-Inspektor und suche im Webspeicher nach einem Cookie namens 'wh'.
Der Wert dieses Cookies ist eine Zahl, die Region-ID.
""",
)
overview_url_template = "https://endpoints.leaflets.schwarz/v4/overview/?region_id={region_id}&client_locale=lidl/{language_code_lower}-{language_code_upper}"
subcategory_name = "Aktionsprospekte"
category_name = "Filial"
flyer_name = "Aktion"
flyer_index = 0
[docs]
@override
def _get_catalog_data(self) -> None:
overview_response = self._client.get(
self.overview_url_template.format(
region_id=self._configuration.region_id,
language_code_lower=self.region.language_code.lower(),
language_code_upper=self.region.language_code.upper(),
)
)
overview_category = next(
(
category
for category in overview_response.json()["categories"]
if self.category_name in category["name"]
),
None,
)
if overview_category is None:
raise CatalogUnavailableWarning
overview_subcategory = next(
(
subcategory
for subcategory in overview_category["subcategories"]
if self.subcategory_name in subcategory["name"]
),
None,
)
if overview_subcategory is None:
raise CatalogUnavailableWarning
flyers = [
flyer
for flyer in overview_subcategory["flyers"]
if self.flyer_name in flyer["name"]
]
try:
flyer_data = flyers[self.flyer_index]
except IndexError as index_error:
raise CatalogUnavailableWarning from index_error
try:
flyer_json_url = flyer_data["flyerJson"]
except IndexError as index_error:
raise CatalogUnavailableWarning from index_error
self.flyer_json = self._client.get(flyer_json_url).json()
[docs]
@override
def _get_page(self, page_number: PageNumber) -> bytes:
try:
url = self.flyer_json["flyer"]["pages"][int(page_number)]["zoom"]
except IndexError as error:
raise PagesExhausted from error
return self._client.get(url).content
[docs]
@override
def _get_valid_since(self) -> datetime:
return datetime.strptime(
self.flyer_json["flyer"]["offerStartDate"], "%Y-%m-%d"
).replace(tzinfo=self.region.timezone)
[docs]
@override
def _get_valid_until(self) -> datetime:
return datetime.strptime(
self.flyer_json["flyer"]["offerEndDate"], "%Y-%m-%d"
).replace(tzinfo=self.region.timezone) + timedelta(days=1)
[docs]
class LidlDeutschlandPreview(Preview, LidlDeutschland):
"""Provider class for Lidl preview catalog for next week."""
uid = LidlDeutschland.uid + "-preview"
name = LidlDeutschland.name + "-Vorschau"
description = LidlDeutschland.description + " nächste Woche"
flyer_index = 1
[docs]
@override
def _get_preview_timedelta(self) -> timedelta:
return timedelta(days=7)
[docs]
class LidlDeutschlandPrepreview(LidlDeutschlandPreview):
"""Provider class for Lidl preview catalog for second-next week."""
uid = LidlDeutschlandPreview.uid + "-2"
name = LidlDeutschland.name + "-Vorvorschau"
description = LidlDeutschland.description + " übernächste Woche"
flyer_index = 2
[docs]
@override
def _get_preview_timedelta(self) -> timedelta:
return timedelta(days=14)
[docs]
class LidlItalia(LidlDeutschland):
"""Provider class for the current italian Lidl catalog."""
uid = "lidl-it"
region = Italy
description = "Offerte Lidl"