Source code for cata_log_hub.providers.kaufland
# 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
[docs]
class KauflandWoche(Provider):
"""Provider class for Kaufland Wochenangebote catalog."""
uid = "kaufland-de"
name = "Kaufland"
description = "Kaufland Angebote"
[docs]
class Configuration(Provider.Configuration):
filial_id: str = Field(
default="0",
description="""ID der Kaufland-Filiale.
Öffne filiale.kaufland.de und wähle deine Filiale aus.
Öffne den Web-Inspektor und suche im Webspeicher nach einem Cookie namens 'x-aem-variant.
Der Wert des Cookies beginnt mit DE.
Die Zahl danach ist die Filial-ID.
""",
)
url = "https://filiale.kaufland.de/prospekte.html"
region = Germany
first_page_number = 0
overview_url_template = "https://endpoints.leaflets.schwarz/v4/overview/?region_id={filial_id}&client_locale=kaufland/{language_code_lower}-{language_code_upper}"
subcategory_name = "KDZ1"
flyer_name = "Prospekt"
flyer_index = 0
[docs]
@override
def _get_catalog_data(self) -> None:
overview_response = self._client.get(
self.overview_url_template.format(
filial_id=self._configuration.filial_id,
language_code_lower=self.region.language_code.lower(),
language_code_upper=self.region.language_code.upper(),
)
)
try:
category_data = overview_response.json()["categories"][0]
except IndexError as index_error:
raise CatalogUnavailableWarning from index_error
overview_subcategory = next(
(
subcategory
for subcategory in category_data["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
flyer_json_url = flyer_data["flyerJson"]
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 KauflandWochePreview(Preview, KauflandWoche):
"""Provider class for Kaufland Wochenangebote preview catalog for next week."""
uid = KauflandWoche.uid + "-preview"
name = KauflandWoche.name + "-Vorschau"
description = KauflandWoche.description + " im nächsten Katalog"
[docs]
@override
def _get_preview_timedelta(self) -> timedelta:
return timedelta(days=7)
flyer_index = 1
[docs]
class KauflandSonder(KauflandWoche):
"""Provider class for Kaufland Sonderangebote catalog."""
uid = "kaufland-sonder-de"
name = "Kaufland Sonderprospekt"
description = "Kaufland Sonderangebote"
flyer_index = 0
subcategory_name = "KDZ2"