Files
eventlens/backend/app/providers/eventim.py
T
2026-04-17 16:39:13 +02:00

140 lines
4.4 KiB
Python

from datetime import datetime
import logging
import requests
from app.config import settings
from app.models import RegionScope, WatchType
from app.providers.utils import normalize_search_text
logger = logging.getLogger(__name__)
class EventimProvider:
base_url = "https://public-api.eventim.com/websearch/search/api/exploration/v1/products"
source_name = "eventim"
def is_configured(self) -> bool:
return settings.eventim_enabled
def search_events(
self,
term: str,
watch_type: WatchType,
region_scope: RegionScope,
) -> list[dict]:
if not self.is_configured():
return []
params = {
"webId": "web__eventim-de",
"language": "de",
"page": 1,
"sort": "DateAsc",
"top": 50,
"search_term": term,
}
if region_scope == RegionScope.hamburg:
params["city_names"] = "Hamburg"
response = requests.get(
self.base_url,
params=params,
headers={
"Accept": "application/json",
"User-Agent": (
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36"
),
},
timeout=30,
)
if response.status_code in {403, 429}:
logger.warning(
"Eventim API blocked request with status %s for term '%s'.",
response.status_code,
term,
)
setattr(self, "last_status", "blocked")
setattr(
self,
"last_message",
f"Eventim API blocked request with status {response.status_code} for term '{term}'.",
)
return []
response.raise_for_status()
payload = response.json()
products = payload.get("products") or []
setattr(self, "last_status", "ok")
setattr(
self,
"last_message",
f"Eventim returned {len(products)} raw products for term '{term}'.",
)
results: list[dict] = []
normalized_term = normalize_search_text(term)
for product in products:
title = product.get("name") or ""
attractions = product.get("attractions") or []
attraction_names = [entry.get("name", "") for entry in attractions]
if watch_type == WatchType.artist:
haystack = normalize_search_text(" ".join(attraction_names + [title]))
if normalized_term not in haystack:
continue
elif normalized_term not in normalize_search_text(title):
continue
live_data = product.get("typeAttributes", {}).get("liveEntertainment", {})
location = live_data.get("location") or {}
city = location.get("city")
if region_scope == RegionScope.hamburg and (city or "").casefold() != "hamburg":
continue
event_date = None
start_date = live_data.get("startDate")
if start_date:
try:
event_date = datetime.fromisoformat(
start_date.replace("Z", "+00:00")
).replace(tzinfo=None)
except ValueError:
event_date = None
url = self._build_url(product)
country_code = "DE" if region_scope == RegionScope.germany or city else None
results.append(
{
"external_id": str(product.get("productId") or url or title),
"title": title,
"matched_term": term,
"venue_name": location.get("name"),
"city": city,
"country_code": country_code,
"event_date": event_date,
"ticket_url": url,
"image_url": None,
"raw_payload": product,
}
)
return results
def _build_url(self, product: dict) -> str | None:
direct_link = product.get("link")
if direct_link:
return direct_link
url = product.get("url") or {}
domain = url.get("domain")
path = url.get("path")
if domain and path:
return f"{domain}{path}"
return None