135 lines
4.4 KiB
Python
135 lines
4.4 KiB
Python
from datetime import datetime
|
|
from urllib.parse import quote
|
|
|
|
import requests
|
|
|
|
from app.config import settings
|
|
from app.models import RegionScope, WatchType
|
|
from app.providers.utils import normalize_search_text
|
|
|
|
|
|
class BandsintownProvider:
|
|
base_url = "https://rest.bandsintown.com"
|
|
source_name = "bandsintown"
|
|
|
|
def is_configured(self) -> bool:
|
|
return bool(settings.bandsintown_app_id)
|
|
|
|
def search_events(
|
|
self,
|
|
term: str,
|
|
watch_type: WatchType,
|
|
region_scope: RegionScope,
|
|
) -> list[dict]:
|
|
if not self.is_configured():
|
|
setattr(self, "last_status", "ok")
|
|
setattr(
|
|
self,
|
|
"last_message",
|
|
"Bandsintown ist nicht konfiguriert. Bitte eine gueltige BANDSINTOWN_APP_ID setzen.",
|
|
)
|
|
return []
|
|
|
|
if watch_type != WatchType.artist:
|
|
setattr(self, "last_status", "ok")
|
|
setattr(
|
|
self,
|
|
"last_message",
|
|
f"Bandsintown skipped non-artist watch item '{term}'.",
|
|
)
|
|
return []
|
|
|
|
artist_name = quote(term, safe="")
|
|
url = f"{self.base_url}/artists/{artist_name}/events"
|
|
response = requests.get(
|
|
url,
|
|
params={"app_id": settings.bandsintown_app_id, "date": "upcoming"},
|
|
timeout=30,
|
|
)
|
|
|
|
if response.status_code in {401, 403, 429}:
|
|
setattr(self, "last_status", "blocked")
|
|
setattr(
|
|
self,
|
|
"last_message",
|
|
(
|
|
f"Bandsintown rejected app_id for '{term}' "
|
|
f"with status {response.status_code}."
|
|
),
|
|
)
|
|
return []
|
|
|
|
if response.status_code == 404:
|
|
setattr(self, "last_status", "ok")
|
|
setattr(
|
|
self,
|
|
"last_message",
|
|
f"Bandsintown found no artist match for '{term}'.",
|
|
)
|
|
return []
|
|
|
|
response.raise_for_status()
|
|
payload = response.json()
|
|
if not isinstance(payload, list):
|
|
setattr(self, "last_status", "error")
|
|
setattr(
|
|
self,
|
|
"last_message",
|
|
f"Bandsintown returned an unexpected payload for '{term}'.",
|
|
)
|
|
return []
|
|
setattr(self, "last_status", "ok")
|
|
setattr(
|
|
self,
|
|
"last_message",
|
|
f"Bandsintown returned {len(payload)} raw events for term '{term}'.",
|
|
)
|
|
|
|
results: list[dict] = []
|
|
normalized_term = normalize_search_text(term)
|
|
for item in payload:
|
|
venue = item.get("venue") or {}
|
|
city = venue.get("city")
|
|
country = venue.get("country")
|
|
|
|
if region_scope == RegionScope.hamburg and (city or "").casefold() != "hamburg":
|
|
continue
|
|
if region_scope == RegionScope.germany and (country or "").casefold() != "germany":
|
|
continue
|
|
|
|
datetime_value = item.get("datetime")
|
|
event_date = None
|
|
if datetime_value:
|
|
try:
|
|
event_date = datetime.fromisoformat(
|
|
datetime_value.replace("Z", "+00:00")
|
|
).replace(tzinfo=None)
|
|
except ValueError:
|
|
event_date = None
|
|
|
|
offers = item.get("offers") or []
|
|
ticket_url = next((offer.get("url") for offer in offers if offer.get("url")), None)
|
|
|
|
title = item.get("title") or f"{term} live"
|
|
artist_name = item.get("artist", {}).get("name", term)
|
|
haystack = normalize_search_text(f"{artist_name} {title}")
|
|
if normalized_term not in haystack:
|
|
continue
|
|
|
|
results.append(
|
|
{
|
|
"external_id": str(item.get("id")),
|
|
"title": title,
|
|
"matched_term": term,
|
|
"venue_name": venue.get("name"),
|
|
"city": city,
|
|
"country_code": "DE" if country == "Germany" else country,
|
|
"event_date": event_date,
|
|
"ticket_url": ticket_url or item.get("url"),
|
|
"image_url": item.get("artist", {}).get("thumb_url"),
|
|
"raw_payload": item,
|
|
}
|
|
)
|
|
|
|
return results
|