|
|
@@ -1,5 +1,6 @@
|
|
|
from __future__ import annotations
|
|
|
|
|
|
+import json
|
|
|
import logging
|
|
|
from dataclasses import dataclass
|
|
|
from datetime import datetime, timedelta
|
|
|
@@ -22,10 +23,12 @@ from .const import (
|
|
|
CONF_PASSWORD,
|
|
|
CONF_USERNAME,
|
|
|
DOMAIN,
|
|
|
+ RESERVATION_TITLE_PREFIX,
|
|
|
SCAN_INTERVAL_MINUTES,
|
|
|
SENSOR_KEYS,
|
|
|
STORE_CURRENT,
|
|
|
STORE_NEXT,
|
|
|
+ X_DATA_PROP,
|
|
|
default_option_key,
|
|
|
)
|
|
|
from .models import ReservationData, merge
|
|
|
@@ -251,50 +254,55 @@ class RoomCalCoordinator(DataUpdateCoordinator[CoordinatorData]):
|
|
|
|
|
|
|
|
|
def _parse_event(event: caldav.Event) -> ReservationData | None:
|
|
|
- """Extract ReservationData from a caldav Event. Returns None if unusable."""
|
|
|
+ """Extract ReservationData from a caldav Event. Returns None if not a reservation event."""
|
|
|
try:
|
|
|
cal = Calendar.from_ical(event.data)
|
|
|
for component in cal.walk():
|
|
|
if component.name != "VEVENT":
|
|
|
continue
|
|
|
|
|
|
- def get(prop: str) -> str | None:
|
|
|
- val = component.get(prop)
|
|
|
- return str(val) if val is not None else None
|
|
|
+ title = str(component.get("SUMMARY", ""))
|
|
|
+ if not title.startswith(RESERVATION_TITLE_PREFIX):
|
|
|
+ return None
|
|
|
|
|
|
dtstart = component.get("DTSTART")
|
|
|
+ if dtstart is None:
|
|
|
+ return None
|
|
|
+ dt = dtstart.dt
|
|
|
+ if not isinstance(dt, datetime):
|
|
|
+ return None # all-day event, not a reservation
|
|
|
+
|
|
|
+ check_in_date = dt.date()
|
|
|
+ check_in_time = dt.time()
|
|
|
+ check_out_date = None
|
|
|
+ check_out_time = None
|
|
|
+
|
|
|
dtend = component.get("DTEND")
|
|
|
+ if dtend is not None:
|
|
|
+ dt_end = dtend.dt
|
|
|
+ if isinstance(dt_end, datetime):
|
|
|
+ check_out_date = dt_end.date()
|
|
|
+ check_out_time = dt_end.time()
|
|
|
|
|
|
- check_in_date: Any = None
|
|
|
- check_in_time: Any = None
|
|
|
- check_out_date: Any = None
|
|
|
- check_out_time: Any = None
|
|
|
-
|
|
|
- if dtstart:
|
|
|
- dt = dtstart.dt
|
|
|
- if isinstance(dt, datetime):
|
|
|
- check_in_date = dt.date()
|
|
|
- check_in_time = dt.time()
|
|
|
- else:
|
|
|
- check_in_date = dt
|
|
|
-
|
|
|
- if dtend:
|
|
|
- dt = dtend.dt
|
|
|
- if isinstance(dt, datetime):
|
|
|
- check_out_date = dt.date()
|
|
|
- check_out_time = dt.time()
|
|
|
- else:
|
|
|
- check_out_date = dt
|
|
|
+ x_data_prop = component.get(X_DATA_PROP)
|
|
|
+ x_data_str = str(x_data_prop) if x_data_prop is not None else None
|
|
|
+ if not x_data_str:
|
|
|
+ return None
|
|
|
+
|
|
|
+ try:
|
|
|
+ d = json.loads(x_data_str)
|
|
|
+ except (ValueError, TypeError):
|
|
|
+ _LOGGER.debug("Failed to parse X-ROOMCAL-DATA JSON in event %r", title)
|
|
|
+ return None
|
|
|
|
|
|
return ReservationData(
|
|
|
- guest_name=get("SUMMARY"),
|
|
|
+ guest_name=d.get("guest_name"),
|
|
|
check_in_date=check_in_date,
|
|
|
check_in_time=check_in_time,
|
|
|
check_out_date=check_out_date,
|
|
|
check_out_time=check_out_time,
|
|
|
- wifi_ssid=get("X-WIFI-SSID"),
|
|
|
- wifi_key=get("X-WIFI-KEY"),
|
|
|
- door_code=get("X-DOOR-CODE"),
|
|
|
+ wifi_key=d.get("wifi_key"),
|
|
|
+ door_code=d.get("door_code"),
|
|
|
)
|
|
|
except Exception:
|
|
|
_LOGGER.debug("Failed to parse event", exc_info=True)
|