Download app/services/calcom.py from nvikou/ReplyFlow: direct link, hf CLI and curl.
- Browser
- Download file 4.93 kB
-
https://huggingface.co/spaces/nvikou/ReplyFlow/resolve/main/app/services/calcom.py
- Command line
-
hf download hf://spaces/nvikou/ReplyFlow/app/services/calcom.py
-
curl -L -o calcom.py https://huggingface.co/spaces/nvikou/ReplyFlow/resolve/main/app/services/calcom.py
4.93 kB
| """Cal.com appointment integration with demo fallback.""" | |
| from __future__ import annotations | |
| import logging | |
| from datetime import datetime, timedelta, timezone | |
| from typing import Any, Optional | |
| from uuid import uuid4 | |
| import httpx | |
| from app.config import get_settings | |
| logger = logging.getLogger(__name__) | |
| async def get_available_slots( | |
| count: int = 3, | |
| ) -> list[dict[str, str]]: | |
| """Return upcoming available slots.""" | |
| settings = get_settings() | |
| if settings.has_calcom: | |
| try: | |
| slots = await _fetch_calcom_slots(count) | |
| if slots: | |
| return slots | |
| except Exception as exc: # noqa: BLE001 | |
| logger.exception("Cal.com slots failed: %s", exc) | |
| return _demo_slots(count) | |
| async def book_slot( | |
| *, | |
| slot_start: str, | |
| slot_end: str, | |
| attendee_name: Optional[str], | |
| attendee_email: Optional[str], | |
| ) -> dict[str, Any]: | |
| """Create a booking via Cal.com or demo store.""" | |
| settings = get_settings() | |
| if settings.has_calcom: | |
| try: | |
| return await _book_calcom( | |
| slot_start=slot_start, | |
| slot_end=slot_end, | |
| attendee_name=attendee_name or "Guest", | |
| attendee_email=attendee_email | |
| or "guest@example.com", | |
| ) | |
| except Exception as exc: # noqa: BLE001 | |
| logger.exception("Cal.com booking failed: %s", exc) | |
| return { | |
| "external_id": f"demo-{uuid4().hex[:10]}", | |
| "slot_start": slot_start, | |
| "slot_end": slot_end, | |
| "status": "confirmed", | |
| "demo": True, | |
| } | |
| def _demo_slots(count: int) -> list[dict[str, str]]: | |
| now = datetime.now(timezone.utc).replace( | |
| minute=0, second=0, microsecond=0 | |
| ) | |
| slots: list[dict[str, str]] = [] | |
| cursor = now + timedelta(days=1) | |
| while len(slots) < count: | |
| if cursor.weekday() < 5 and cursor.hour in (10, 14, 16): | |
| end = cursor + timedelta(minutes=30) | |
| slots.append( | |
| { | |
| "start": cursor.isoformat(), | |
| "end": end.isoformat(), | |
| "label": cursor.strftime( | |
| "%a %d %b %Y · %H:%M UTC" | |
| ), | |
| } | |
| ) | |
| cursor += timedelta(hours=2) | |
| return slots | |
| async def _fetch_calcom_slots(count: int) -> list[dict[str, str]]: | |
| settings = get_settings() | |
| start = datetime.now(timezone.utc) | |
| end = start + timedelta(days=14) | |
| url = "https://api.cal.com/v1/slots" | |
| params = { | |
| "apiKey": settings.calcom_api_key, | |
| "eventTypeId": settings.calcom_event_type_id, | |
| "startTime": start.isoformat(), | |
| "endTime": end.isoformat(), | |
| } | |
| async with httpx.AsyncClient(timeout=20) as client: | |
| response = await client.get(url, params=params) | |
| response.raise_for_status() | |
| data = response.json() | |
| slots: list[dict[str, str]] = [] | |
| raw_slots = data.get("slots") or data | |
| if isinstance(raw_slots, dict): | |
| for day_slots in raw_slots.values(): | |
| if not isinstance(day_slots, list): | |
| continue | |
| for item in day_slots: | |
| start_s = item.get("time") or item.get("start") | |
| if not start_s: | |
| continue | |
| start_dt = datetime.fromisoformat( | |
| start_s.replace("Z", "+00:00") | |
| ) | |
| end_dt = start_dt + timedelta(minutes=30) | |
| slots.append( | |
| { | |
| "start": start_dt.isoformat(), | |
| "end": end_dt.isoformat(), | |
| "label": start_dt.strftime( | |
| "%a %d %b %Y · %H:%M UTC" | |
| ), | |
| } | |
| ) | |
| if len(slots) >= count: | |
| return slots | |
| return slots[:count] | |
| async def _book_calcom( | |
| *, | |
| slot_start: str, | |
| slot_end: str, | |
| attendee_name: str, | |
| attendee_email: str, | |
| ) -> dict[str, Any]: | |
| settings = get_settings() | |
| url = "https://api.cal.com/v1/bookings" | |
| payload = { | |
| "eventTypeId": int(settings.calcom_event_type_id), | |
| "start": slot_start, | |
| "responses": { | |
| "name": attendee_name, | |
| "email": attendee_email, | |
| }, | |
| "timeZone": settings.demo_timezone, | |
| "language": "en", | |
| "metadata": {}, | |
| } | |
| params = {"apiKey": settings.calcom_api_key} | |
| async with httpx.AsyncClient(timeout=20) as client: | |
| response = await client.post( | |
| url, params=params, json=payload | |
| ) | |
| response.raise_for_status() | |
| data = response.json() | |
| return { | |
| "external_id": str( | |
| data.get("id") or data.get("uid") or uuid4() | |
| ), | |
| "slot_start": slot_start, | |
| "slot_end": slot_end, | |
| "status": "confirmed", | |
| "demo": False, | |
| } | |