Jambase zugefügt
This commit is contained in:
@@ -9,6 +9,8 @@ DB_NAME=eventlens
|
|||||||
MYSQL_ROOT_PASSWORD=rootsecret
|
MYSQL_ROOT_PASSWORD=rootsecret
|
||||||
|
|
||||||
TICKETMASTER_API_KEY=
|
TICKETMASTER_API_KEY=
|
||||||
|
JAMBASE_API_KEY=
|
||||||
|
JAMBASE_USER_AGENT=eventlens/0.1
|
||||||
BANDSINTOWN_APP_ID=
|
BANDSINTOWN_APP_ID=
|
||||||
EVENTIM_ENABLED=true
|
EVENTIM_ENABLED=true
|
||||||
POLL_INTERVAL_HOURS=6
|
POLL_INTERVAL_HOURS=6
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ API-Statusinfo findest du unter `http://127.0.0.1:8001/api`.
|
|||||||
## Wichtige Umgebungsvariablen
|
## Wichtige Umgebungsvariablen
|
||||||
|
|
||||||
- `TICKETMASTER_API_KEY`: Ticketmaster Discovery API
|
- `TICKETMASTER_API_KEY`: Ticketmaster Discovery API
|
||||||
|
- `JAMBASE_API_KEY`: JamBase Data API Bearer Token
|
||||||
|
- `JAMBASE_USER_AGENT`: eigene App-Kennung fuer JamBase-Requests
|
||||||
- `BANDSINTOWN_APP_ID`: echte Bandsintown App-ID fuer Artist-Events
|
- `BANDSINTOWN_APP_ID`: echte Bandsintown App-ID fuer Artist-Events
|
||||||
- `EVENTIM_ENABLED`: aktiviert den Eventim-Website-Provider
|
- `EVENTIM_ENABLED`: aktiviert den Eventim-Website-Provider
|
||||||
- `EVENTLENS_AUTH_USERNAME`, `EVENTLENS_AUTH_PASSWORD`: optionaler Passwortschutz fuer Webfrontend und API
|
- `EVENTLENS_AUTH_USERNAME`, `EVENTLENS_AUTH_PASSWORD`: optionaler Passwortschutz fuer Webfrontend und API
|
||||||
@@ -110,6 +112,7 @@ sudo docker compose up -d --build
|
|||||||
## Provider-Hinweise
|
## Provider-Hinweise
|
||||||
|
|
||||||
- Ticketmaster nutzt die offizielle Discovery API.
|
- Ticketmaster nutzt die offizielle Discovery API.
|
||||||
|
- JamBase nutzt die JamBase Data API ueber `https://api.data.jambase.com/v3/events` mit Bearer-Token und `User-Agent`.
|
||||||
- Bandsintown nutzt die offizielle Artist-Events-API und arbeitet deshalb vor allem fuer Watchlist-Eintraege vom Typ `artist`.
|
- Bandsintown nutzt die offizielle Artist-Events-API und arbeitet deshalb vor allem fuer Watchlist-Eintraege vom Typ `artist`.
|
||||||
- Eventim nutzt eine beobachtete oeffentliche JSON-Web-API von eventim.de. Das ist keine offiziell dokumentierte Developer-API, aber stabiler als HTML-Scraping. Aenderungen an diesem Web-Endpoint koennen trotzdem Anpassungen noetig machen.
|
- Eventim nutzt eine beobachtete oeffentliche JSON-Web-API von eventim.de. Das ist keine offiziell dokumentierte Developer-API, aber stabiler als HTML-Scraping. Aenderungen an diesem Web-Endpoint koennen trotzdem Anpassungen noetig machen.
|
||||||
- Eventim kann serverseitig durch Eventim/Akamai geblockt werden. In dem Fall liefert `eventlens` bewusst keine falschen Treffer, sondern ueberspringt den Provider und schreibt einen Hinweis ins Backend-Log.
|
- Eventim kann serverseitig durch Eventim/Akamai geblockt werden. In dem Fall liefert `eventlens` bewusst keine falschen Treffer, sondern ueberspringt den Provider und schreibt einen Hinweis ins Backend-Log.
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ class Settings:
|
|||||||
db_name = os.getenv("DB_NAME", "eventlens")
|
db_name = os.getenv("DB_NAME", "eventlens")
|
||||||
|
|
||||||
ticketmaster_api_key = os.getenv("TICKETMASTER_API_KEY", "")
|
ticketmaster_api_key = os.getenv("TICKETMASTER_API_KEY", "")
|
||||||
|
jambase_api_key = os.getenv("JAMBASE_API_KEY", "")
|
||||||
|
jambase_user_agent = os.getenv("JAMBASE_USER_AGENT", "eventlens/0.1")
|
||||||
bandsintown_app_id = os.getenv("BANDSINTOWN_APP_ID", "")
|
bandsintown_app_id = os.getenv("BANDSINTOWN_APP_ID", "")
|
||||||
eventim_enabled = os.getenv("EVENTIM_ENABLED", "true").lower() == "true"
|
eventim_enabled = os.getenv("EVENTIM_ENABLED", "true").lower() == "true"
|
||||||
poll_interval_hours = int(os.getenv("POLL_INTERVAL_HOURS", "6"))
|
poll_interval_hours = int(os.getenv("POLL_INTERVAL_HOURS", "6"))
|
||||||
|
|||||||
@@ -113,6 +113,7 @@
|
|||||||
<select id="provider-filter">
|
<select id="provider-filter">
|
||||||
<option value="all">Alle Quellen</option>
|
<option value="all">Alle Quellen</option>
|
||||||
<option value="ticketmaster">Ticketmaster</option>
|
<option value="ticketmaster">Ticketmaster</option>
|
||||||
|
<option value="jambase">JamBase</option>
|
||||||
<option value="bandsintown">Bandsintown</option>
|
<option value="bandsintown">Bandsintown</option>
|
||||||
<option value="eventim">Eventim</option>
|
<option value="eventim">Eventim</option>
|
||||||
<option value="barclays_arena">Barclays Arena</option>
|
<option value="barclays_arena">Barclays Arena</option>
|
||||||
|
|||||||
@@ -142,6 +142,7 @@ function prettifyProviderName(value) {
|
|||||||
|
|
||||||
const names = {
|
const names = {
|
||||||
ticketmaster: "Ticketmaster",
|
ticketmaster: "Ticketmaster",
|
||||||
|
jambase: "JamBase",
|
||||||
bandsintown: "Bandsintown",
|
bandsintown: "Bandsintown",
|
||||||
eventim: "Eventim",
|
eventim: "Eventim",
|
||||||
barclays_arena: "Barclays Arena",
|
barclays_arena: "Barclays Arena",
|
||||||
@@ -154,6 +155,7 @@ function prettifyProviderName(value) {
|
|||||||
function getProviderClass(value) {
|
function getProviderClass(value) {
|
||||||
const classes = {
|
const classes = {
|
||||||
ticketmaster: "provider-ticketmaster",
|
ticketmaster: "provider-ticketmaster",
|
||||||
|
jambase: "provider-jambase",
|
||||||
bandsintown: "provider-bandsintown",
|
bandsintown: "provider-bandsintown",
|
||||||
eventim: "provider-eventim",
|
eventim: "provider-eventim",
|
||||||
barclays_arena: "provider-barclays-arena",
|
barclays_arena: "provider-barclays-arena",
|
||||||
|
|||||||
@@ -352,6 +352,11 @@ button:hover,
|
|||||||
color: var(--primary-dark);
|
color: var(--primary-dark);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.provider-jambase {
|
||||||
|
background: rgba(17, 108, 110, 0.13);
|
||||||
|
color: #0d5f61;
|
||||||
|
}
|
||||||
|
|
||||||
.provider-eventim {
|
.provider-eventim {
|
||||||
background: rgba(86, 60, 153, 0.14);
|
background: rgba(86, 60, 153, 0.14);
|
||||||
color: #4f3790;
|
color: #4f3790;
|
||||||
|
|||||||
@@ -0,0 +1,369 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from app.config import settings
|
||||||
|
from app.models import RegionScope, WatchType
|
||||||
|
from app.providers.utils import normalize_search_text
|
||||||
|
|
||||||
|
|
||||||
|
class JamBaseProvider:
|
||||||
|
base_url = "https://api.data.jambase.com/v3/events"
|
||||||
|
source_name = "jambase"
|
||||||
|
|
||||||
|
def is_configured(self) -> bool:
|
||||||
|
return bool(settings.jambase_api_key and settings.jambase_user_agent)
|
||||||
|
|
||||||
|
def search_events(
|
||||||
|
self,
|
||||||
|
term: str,
|
||||||
|
watch_type: WatchType,
|
||||||
|
region_scope: RegionScope,
|
||||||
|
) -> list[dict]:
|
||||||
|
self.last_status = "ok"
|
||||||
|
self.last_message = ""
|
||||||
|
if not self.is_configured():
|
||||||
|
self.last_status = "ok"
|
||||||
|
self.last_message = "JamBase ist nicht konfiguriert. Bitte JAMBASE_API_KEY setzen."
|
||||||
|
return []
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Bearer {settings.jambase_api_key}",
|
||||||
|
"Accept": "application/json",
|
||||||
|
"User-Agent": settings.jambase_user_agent,
|
||||||
|
}
|
||||||
|
|
||||||
|
payload = None
|
||||||
|
raw_count = 0
|
||||||
|
tried_params: list[str] = []
|
||||||
|
bad_request_messages: list[str] = []
|
||||||
|
for params in self._build_param_candidates(term, watch_type, region_scope):
|
||||||
|
tried_params.append(", ".join(sorted(params.keys())) or "keine")
|
||||||
|
response = requests.get(self.base_url, headers=headers, params=params, timeout=30)
|
||||||
|
|
||||||
|
if response.status_code == 401:
|
||||||
|
self.last_status = "blocked"
|
||||||
|
self.last_message = (
|
||||||
|
f"JamBase hat den API-Key fuer '{term}' nicht akzeptiert (401)."
|
||||||
|
)
|
||||||
|
return []
|
||||||
|
|
||||||
|
if response.status_code == 403:
|
||||||
|
self.last_status = "blocked"
|
||||||
|
self.last_message = (
|
||||||
|
f"JamBase verweigert Zugriff fuer '{term}' (403). "
|
||||||
|
"Pruefe Plan oder Berechtigungen."
|
||||||
|
)
|
||||||
|
return []
|
||||||
|
|
||||||
|
if response.status_code == 400:
|
||||||
|
bad_request_messages.append(
|
||||||
|
self._format_error_response(response, params)
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
|
||||||
|
response.raise_for_status()
|
||||||
|
try:
|
||||||
|
payload = response.json()
|
||||||
|
except ValueError:
|
||||||
|
self.last_status = "error"
|
||||||
|
self.last_message = (
|
||||||
|
f"JamBase lieferte keine gueltige JSON-Antwort fuer '{term}' "
|
||||||
|
f"(Status {response.status_code})."
|
||||||
|
)
|
||||||
|
return []
|
||||||
|
raw_items = self._extract_items(payload)
|
||||||
|
raw_count = len(raw_items)
|
||||||
|
if raw_items:
|
||||||
|
break
|
||||||
|
|
||||||
|
if payload is None:
|
||||||
|
self.last_status = "error"
|
||||||
|
detail = bad_request_messages[0] if bad_request_messages else "Keine Detailmeldung."
|
||||||
|
self.last_message = f"JamBase akzeptierte die Anfrage fuer '{term}' nicht: {detail}"
|
||||||
|
return []
|
||||||
|
|
||||||
|
items = self._extract_items(payload)
|
||||||
|
self.last_status = "ok"
|
||||||
|
|
||||||
|
normalized_term = normalize_search_text(term)
|
||||||
|
results: list[dict] = []
|
||||||
|
seen_ids: set[str] = set()
|
||||||
|
|
||||||
|
for item in items:
|
||||||
|
title = self._get_first(item, "name", "title", "eventName") or term
|
||||||
|
artists = self._extract_artist_names(item)
|
||||||
|
haystack = normalize_search_text(" ".join([title] + artists))
|
||||||
|
if watch_type == WatchType.artist:
|
||||||
|
if normalized_term not in haystack:
|
||||||
|
continue
|
||||||
|
elif normalized_term not in normalize_search_text(title):
|
||||||
|
continue
|
||||||
|
|
||||||
|
city = self._extract_city(item)
|
||||||
|
country_code = self._extract_country_code(item)
|
||||||
|
|
||||||
|
if region_scope == RegionScope.hamburg and normalize_search_text(city) != "hamburg":
|
||||||
|
continue
|
||||||
|
if region_scope == RegionScope.germany and country_code not in {"DE", "GER", "Germany"}:
|
||||||
|
continue
|
||||||
|
|
||||||
|
event_date = self._extract_event_date(item)
|
||||||
|
if event_date and event_date.date() < datetime.utcnow().date():
|
||||||
|
continue
|
||||||
|
|
||||||
|
external_id = str(
|
||||||
|
self._get_first(item, "id", "eventId", "identifier")
|
||||||
|
or item.get("slug")
|
||||||
|
or item.get("url")
|
||||||
|
or f"jambase:{normalize_search_text(title)}:{event_date.isoformat() if event_date else 'na'}"
|
||||||
|
)
|
||||||
|
if external_id in seen_ids:
|
||||||
|
continue
|
||||||
|
seen_ids.add(external_id)
|
||||||
|
|
||||||
|
results.append(
|
||||||
|
{
|
||||||
|
"external_id": external_id,
|
||||||
|
"title": title,
|
||||||
|
"matched_term": term,
|
||||||
|
"venue_name": self._extract_venue_name(item),
|
||||||
|
"city": city,
|
||||||
|
"country_code": country_code,
|
||||||
|
"event_date": event_date,
|
||||||
|
"ticket_url": self._extract_ticket_url(item),
|
||||||
|
"image_url": self._extract_image_url(item),
|
||||||
|
"raw_payload": item,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
self.last_message = (
|
||||||
|
f"JamBase lieferte {raw_count} Roh-Events fuer '{term}', "
|
||||||
|
f"davon {len(results)} passend nach Eventlens-Filtern."
|
||||||
|
)
|
||||||
|
return results
|
||||||
|
|
||||||
|
def _build_param_candidates(
|
||||||
|
self,
|
||||||
|
term: str,
|
||||||
|
watch_type: WatchType,
|
||||||
|
region_scope: RegionScope,
|
||||||
|
) -> list[dict]:
|
||||||
|
base_params = {
|
||||||
|
"apikey": settings.jambase_api_key,
|
||||||
|
"perPage": 100,
|
||||||
|
"eventDateFrom": datetime.utcnow().date().isoformat(),
|
||||||
|
}
|
||||||
|
|
||||||
|
base_params["artistName"] = term
|
||||||
|
|
||||||
|
if region_scope == RegionScope.hamburg:
|
||||||
|
return [
|
||||||
|
base_params
|
||||||
|
| {
|
||||||
|
"geoLatitude": 53.5511,
|
||||||
|
"geoLongitude": 9.9937,
|
||||||
|
"geoRadiusAmount": 35,
|
||||||
|
"geoRadiusUnits": "km",
|
||||||
|
"geoCountryIso2": "DE",
|
||||||
|
},
|
||||||
|
base_params
|
||||||
|
| {
|
||||||
|
"geoLatitude": 53.5511,
|
||||||
|
"geoLongitude": 9.9937,
|
||||||
|
"geoRadiusAmount": 35,
|
||||||
|
"geoRadiusUnits": "km",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
if region_scope == RegionScope.germany:
|
||||||
|
return [base_params | {"geoCountryIso2": "DE"}]
|
||||||
|
|
||||||
|
return [base_params]
|
||||||
|
|
||||||
|
def _extract_items(self, payload) -> list[dict]:
|
||||||
|
if isinstance(payload, list):
|
||||||
|
return [item for item in payload if isinstance(item, dict)]
|
||||||
|
if not isinstance(payload, dict):
|
||||||
|
return []
|
||||||
|
|
||||||
|
for key in ("data", "events", "results", "items"):
|
||||||
|
value = payload.get(key)
|
||||||
|
if isinstance(value, list):
|
||||||
|
return [item for item in value if isinstance(item, dict)]
|
||||||
|
|
||||||
|
embedded = payload.get("_embedded")
|
||||||
|
if isinstance(embedded, dict):
|
||||||
|
for key in ("events", "items"):
|
||||||
|
value = embedded.get(key)
|
||||||
|
if isinstance(value, list):
|
||||||
|
return [item for item in value if isinstance(item, dict)]
|
||||||
|
|
||||||
|
# Some APIs wrap collections inside nested objects like {"data": {"events": [...]}}.
|
||||||
|
for key in ("data", "results"):
|
||||||
|
value = payload.get(key)
|
||||||
|
if isinstance(value, dict):
|
||||||
|
nested_items = self._extract_items(value)
|
||||||
|
if nested_items:
|
||||||
|
return nested_items
|
||||||
|
|
||||||
|
return []
|
||||||
|
|
||||||
|
def _extract_artist_names(self, item: dict) -> list[str]:
|
||||||
|
names: list[str] = []
|
||||||
|
for key in ("artist", "artists", "performer", "performers", "lineup", "acts"):
|
||||||
|
value = item.get(key)
|
||||||
|
if isinstance(value, list):
|
||||||
|
for entry in value:
|
||||||
|
if isinstance(entry, dict):
|
||||||
|
name = self._get_first(entry, "name", "artistName", "title")
|
||||||
|
if name:
|
||||||
|
names.append(name)
|
||||||
|
elif isinstance(entry, str):
|
||||||
|
names.append(entry)
|
||||||
|
elif isinstance(value, dict):
|
||||||
|
name = self._get_first(value, "name", "artistName", "title")
|
||||||
|
if name:
|
||||||
|
names.append(name)
|
||||||
|
return names
|
||||||
|
|
||||||
|
def _extract_venue_name(self, item: dict) -> str | None:
|
||||||
|
venue = item.get("venue")
|
||||||
|
if isinstance(venue, dict):
|
||||||
|
return self._get_first(venue, "name", "venueName", "title")
|
||||||
|
return self._get_first(item, "venue_name", "venueName")
|
||||||
|
|
||||||
|
def _extract_city(self, item: dict) -> str | None:
|
||||||
|
venue = item.get("venue")
|
||||||
|
if isinstance(venue, dict):
|
||||||
|
city = venue.get("city")
|
||||||
|
if isinstance(city, dict):
|
||||||
|
return self._get_first(city, "name")
|
||||||
|
if isinstance(city, str):
|
||||||
|
return city
|
||||||
|
address = venue.get("address")
|
||||||
|
if isinstance(address, dict):
|
||||||
|
locality = address.get("addressLocality")
|
||||||
|
if isinstance(locality, dict):
|
||||||
|
return self._get_first(locality, "name")
|
||||||
|
if isinstance(locality, str):
|
||||||
|
return locality
|
||||||
|
return self._get_first(address, "city")
|
||||||
|
|
||||||
|
location = item.get("location")
|
||||||
|
if isinstance(location, dict):
|
||||||
|
city = location.get("city")
|
||||||
|
if isinstance(city, dict):
|
||||||
|
return self._get_first(city, "name")
|
||||||
|
if isinstance(city, str):
|
||||||
|
return city
|
||||||
|
address = location.get("address")
|
||||||
|
if isinstance(address, dict):
|
||||||
|
locality = address.get("addressLocality")
|
||||||
|
if isinstance(locality, dict):
|
||||||
|
return self._get_first(locality, "name")
|
||||||
|
if isinstance(locality, str):
|
||||||
|
return locality
|
||||||
|
return self._get_first(address, "city")
|
||||||
|
|
||||||
|
return self._get_first(item, "city")
|
||||||
|
|
||||||
|
def _extract_country_code(self, item: dict) -> str | None:
|
||||||
|
for container_name in ("venue", "location"):
|
||||||
|
container = item.get(container_name)
|
||||||
|
if not isinstance(container, dict):
|
||||||
|
continue
|
||||||
|
country = container.get("country")
|
||||||
|
if isinstance(country, dict):
|
||||||
|
return self._get_first(country, "countryCode", "code", "name")
|
||||||
|
if isinstance(country, str):
|
||||||
|
return country
|
||||||
|
address = container.get("address")
|
||||||
|
if isinstance(address, dict):
|
||||||
|
country = address.get("addressCountry")
|
||||||
|
if isinstance(country, dict):
|
||||||
|
return self._get_first(country, "identifier", "alternateName", "name")
|
||||||
|
if isinstance(country, str):
|
||||||
|
return country
|
||||||
|
return self._get_first(address, "countryCode", "country")
|
||||||
|
return self._get_first(item, "country_code", "countryCode", "country")
|
||||||
|
|
||||||
|
def _extract_event_date(self, item: dict) -> datetime | None:
|
||||||
|
for key in ("startDate", "date", "datetime", "eventDate", "startsAt"):
|
||||||
|
value = item.get(key)
|
||||||
|
parsed = self._parse_datetime(value)
|
||||||
|
if parsed:
|
||||||
|
return parsed
|
||||||
|
|
||||||
|
dates = item.get("dates")
|
||||||
|
if isinstance(dates, dict):
|
||||||
|
for key in ("start", "event"):
|
||||||
|
nested = dates.get(key)
|
||||||
|
if isinstance(nested, dict):
|
||||||
|
local_date = self._get_first(nested, "localDate", "date")
|
||||||
|
local_time = self._get_first(nested, "localTime", "time") or "20:00:00"
|
||||||
|
if local_date:
|
||||||
|
try:
|
||||||
|
return datetime.fromisoformat(f"{local_date}T{local_time}")
|
||||||
|
except ValueError:
|
||||||
|
continue
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _extract_ticket_url(self, item: dict) -> str | None:
|
||||||
|
for key in ("url", "ticketUrl", "ticket_url"):
|
||||||
|
value = item.get(key)
|
||||||
|
if isinstance(value, str) and value:
|
||||||
|
return value
|
||||||
|
|
||||||
|
offers = item.get("offers")
|
||||||
|
if isinstance(offers, list):
|
||||||
|
for offer in offers:
|
||||||
|
if not isinstance(offer, dict):
|
||||||
|
continue
|
||||||
|
value = self._get_first(offer, "url", "ticketUrl")
|
||||||
|
if value:
|
||||||
|
return value
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _extract_image_url(self, item: dict) -> str | None:
|
||||||
|
for key in ("image", "image_url", "imageUrl"):
|
||||||
|
value = item.get(key)
|
||||||
|
if isinstance(value, str) and value:
|
||||||
|
return value
|
||||||
|
|
||||||
|
images = item.get("images")
|
||||||
|
if isinstance(images, list):
|
||||||
|
for image in images:
|
||||||
|
if isinstance(image, dict):
|
||||||
|
value = self._get_first(image, "url", "src")
|
||||||
|
if value:
|
||||||
|
return value
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _parse_datetime(self, value) -> datetime | None:
|
||||||
|
if not value or not isinstance(value, str):
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
return datetime.fromisoformat(value.replace("Z", "+00:00")).replace(tzinfo=None)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
for fmt in ("%Y-%m-%d", "%d.%m.%Y"):
|
||||||
|
try:
|
||||||
|
return datetime.strptime(value[:10], fmt)
|
||||||
|
except ValueError:
|
||||||
|
continue
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _format_error_response(self, response: requests.Response, params: dict) -> str:
|
||||||
|
param_names = ", ".join(sorted(params.keys()))
|
||||||
|
body = response.text.strip().replace("\n", " ")
|
||||||
|
if len(body) > 420:
|
||||||
|
body = f"{body[:420]}..."
|
||||||
|
return f"HTTP {response.status_code} mit Parametern [{param_names}]: {body}"
|
||||||
|
|
||||||
|
def _get_first(self, payload: dict, *keys: str) -> str | None:
|
||||||
|
for key in keys:
|
||||||
|
value = payload.get(key)
|
||||||
|
if isinstance(value, str) and value:
|
||||||
|
return value
|
||||||
|
return None
|
||||||
@@ -2,12 +2,14 @@ from app.providers.bandsintown import BandsintownProvider
|
|||||||
from app.providers.barclays_arena import BarclaysArenaProvider
|
from app.providers.barclays_arena import BarclaysArenaProvider
|
||||||
from app.providers.eventim import EventimProvider
|
from app.providers.eventim import EventimProvider
|
||||||
from app.providers.fabrik import FabrikProvider
|
from app.providers.fabrik import FabrikProvider
|
||||||
|
from app.providers.jambase import JamBaseProvider
|
||||||
from app.providers.ticketmaster import TicketmasterProvider
|
from app.providers.ticketmaster import TicketmasterProvider
|
||||||
|
|
||||||
|
|
||||||
def get_providers():
|
def get_providers():
|
||||||
return [
|
return [
|
||||||
TicketmasterProvider(),
|
TicketmasterProvider(),
|
||||||
|
JamBaseProvider(),
|
||||||
BandsintownProvider(),
|
BandsintownProvider(),
|
||||||
EventimProvider(),
|
EventimProvider(),
|
||||||
BarclaysArenaProvider(),
|
BarclaysArenaProvider(),
|
||||||
|
|||||||
+16
-2
@@ -153,10 +153,14 @@ def record_provider_sync_state(
|
|||||||
|
|
||||||
def build_provider_status_message(state: dict) -> str:
|
def build_provider_status_message(state: dict) -> str:
|
||||||
if state["status"] == ProviderStatusType.error:
|
if state["status"] == ProviderStatusType.error:
|
||||||
|
if state["last_message"]:
|
||||||
|
return state["last_message"]
|
||||||
terms = ", ".join(state["error_terms"][:3])
|
terms = ", ".join(state["error_terms"][:3])
|
||||||
return f"Fehler bei {state['provider_name']} fuer: {terms}."
|
return f"Fehler bei {state['provider_name']} fuer: {terms}."
|
||||||
|
|
||||||
if state["status"] == ProviderStatusType.blocked:
|
if state["status"] == ProviderStatusType.blocked:
|
||||||
|
if state["last_message"]:
|
||||||
|
return state["last_message"]
|
||||||
terms = ", ".join(state["blocked_terms"][:3])
|
terms = ", ".join(state["blocked_terms"][:3])
|
||||||
return f"{state['provider_name']} wurde geblockt fuer: {terms}."
|
return f"{state['provider_name']} wurde geblockt fuer: {terms}."
|
||||||
|
|
||||||
@@ -408,7 +412,17 @@ def run_sync(db: Session) -> SyncResult:
|
|||||||
term=watch_item.name,
|
term=watch_item.name,
|
||||||
message=provider_message,
|
message=provider_message,
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception as exc:
|
||||||
|
provider_message = getattr(
|
||||||
|
provider,
|
||||||
|
"last_message",
|
||||||
|
f"Provider sync failed for '{watch_item.name}'.",
|
||||||
|
)
|
||||||
|
if not provider_message:
|
||||||
|
provider_message = (
|
||||||
|
f"{provider.source_name} failed for '{watch_item.name}': "
|
||||||
|
f"{type(exc).__name__}: {exc}"
|
||||||
|
)
|
||||||
logger.exception(
|
logger.exception(
|
||||||
"Provider sync failed for provider=%s watch_item=%s",
|
"Provider sync failed for provider=%s watch_item=%s",
|
||||||
provider.source_name,
|
provider.source_name,
|
||||||
@@ -419,7 +433,7 @@ def run_sync(db: Session) -> SyncResult:
|
|||||||
state=provider_states[provider.source_name],
|
state=provider_states[provider.source_name],
|
||||||
status=ProviderStatusType.error,
|
status=ProviderStatusType.error,
|
||||||
term=watch_item.name,
|
term=watch_item.name,
|
||||||
message=f"Provider sync failed for '{watch_item.name}'.",
|
message=provider_message,
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
for event_data in events:
|
for event_data in events:
|
||||||
|
|||||||
Reference in New Issue
Block a user