from datetime import datetime from urllib.parse import urljoin import requests from bs4 import BeautifulSoup from app.models import RegionScope, WatchType from app.providers.utils import normalize_search_text class FabrikProvider: source_name = "fabrik" events_url = "https://fabrik.de/programm" def search_events( self, term: str, watch_type: WatchType, region_scope: RegionScope, ) -> list[dict]: response = requests.get( self.events_url, headers={"User-Agent": "Mozilla/5.0"}, timeout=30, ) response.raise_for_status() soup = BeautifulSoup(response.text, "html.parser") normalized_term = normalize_search_text(term) results: list[dict] = [] for event_block in soup.select(".programm_termin"): detail_link = event_block.select_one('a[href*="/veranstaltungsdetail/"][title]') if detail_link is None: continue title = detail_link.get("title") or detail_link.get_text(" ", strip=True) if not title: continue block_text = event_block.get_text(" ", strip=True) haystack = normalize_search_text(f"{title} {block_text}") if normalized_term not in haystack: continue date_el = event_block.select_one(".programm_termin_lh_datum a") date_text = date_el.get_text(" ", strip=True) if date_el else "" event_date = self._parse_fabrik_date(date_text) href = detail_link["href"] results.append( { "external_id": href, "title": title, "matched_term": term, "venue_name": "Fabrik", "city": "Hamburg", "country_code": "DE", "event_date": event_date, "ticket_url": urljoin(self.events_url, href), "image_url": None, "raw_payload": { "title": title, "date_text": date_text, "href": href, }, } ) unique_results: dict[str, dict] = {} for result in results: unique_results[result["external_id"]] = result self.last_status = "ok" self.last_message = ( f"Fabrik returned {len(unique_results)} matched events for term '{term}'." ) return list(unique_results.values()) def _parse_fabrik_date(self, value: str) -> datetime | None: cleaned = value.replace(" ", "") if len(cleaned) < 10: return None date_value = cleaned[-10:] try: return datetime.strptime(date_value, "%d.%m.%Y") except ValueError: return None