140 lines
4.4 KiB
Python
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
|