mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-06-16 17:55:26 -04:00
fix(caldav): pull Google Calendar events from the events collection, not the /user principal (#2531)
* fix(caldav): pull Google Calendar events from the events collection, not the /user principal Google serves its CalDAV principal at .../caldav/v2/<id>/user but events live under .../caldav/v2/<id>/events. The caldav library's principal->home-set discovery does not reliably enumerate calendars from Google's /user endpoint, so _sync_blocking fell into its 'treat the URL as a single calendar' fallback and ran every calendar-query REPORT against the principal URL. /user holds no VEVENTs, so the REPORT returned a clean but empty 200 for every date range: auth succeeded, the calendar stayed empty (Apple Calendar works because iCloud exposes standard discovery at the pasted URL). Add _google_caldav_events_url() to map a recognised Google principal URL to its events collection, and route both discovery-less fallbacks through _open_url_as_calendar() so Google syncs hit /events while other servers' URLs are used unchanged. Fixes #2507 * fix(caldav): also map Google's legacy www.google.com/calendar/dav principal URL Some Google accounts authenticate against the older CalDAV endpoint (https://www.google.com/calendar/dav/<id>/user) rather than the newer apidata.googleusercontent.com/caldav/v2 form (reported on #2507). Both have the same principal-vs-events split, so map the legacy /user URL to its /events collection as well. The legacy branch is gated on the /calendar/dav/ path so an unrelated www.google.com URL ending in /user is left untouched.
This commit is contained in:
+48
-2
@@ -166,6 +166,52 @@ def _find_existing_event(db, pending, uid_val, calendar_id):
|
||||
).first()
|
||||
|
||||
|
||||
def _google_caldav_events_url(url: str) -> str | None:
|
||||
"""Map a Google CalDAV *principal* URL to its event-collection URL.
|
||||
|
||||
Google serves the principal at ``…/user`` but events live under ``…/events``
|
||||
— the ``/user`` resource holds no VEVENTs. The `caldav` library's
|
||||
principal→home-set discovery does not reliably enumerate calendars from
|
||||
Google's ``/user`` endpoint, so the sync falls into the "treat the URL as a
|
||||
single calendar" fallback below. Pointed at ``/user`` that fallback issues
|
||||
every calendar-query REPORT against the principal, which returns a clean but
|
||||
empty 200 for all date ranges — the calendar shows no events even though
|
||||
auth succeeded (issue #2507).
|
||||
|
||||
Both Google CalDAV endpoint forms are handled, since some accounts only
|
||||
authenticate against one of them:
|
||||
- newer: ``https://apidata.googleusercontent.com/caldav/v2/<id>/user``
|
||||
- legacy: ``https://www.google.com/calendar/dav/<id>/user``
|
||||
|
||||
Returns the events URL for a recognised Google principal URL, else None so
|
||||
the caller keeps the original URL unchanged.
|
||||
"""
|
||||
parts = urlparse(url)
|
||||
host = (parts.hostname or "").lower()
|
||||
path = parts.path.rstrip("/")
|
||||
if not path.endswith("/user"):
|
||||
return None
|
||||
is_google = (
|
||||
host.endswith("googleusercontent.com") # newer /caldav/v2 form
|
||||
or (host in ("www.google.com", "google.com") and "/calendar/dav/" in path) # legacy form
|
||||
)
|
||||
if not is_google:
|
||||
return None
|
||||
new_path = path[: -len("/user")] + "/events"
|
||||
return urlunparse(parts._replace(path=new_path))
|
||||
|
||||
|
||||
def _open_url_as_calendar(client, url: str):
|
||||
"""Open ``url`` as a single calendar collection.
|
||||
|
||||
Used when principal discovery yields no calendars. Google's principal URL
|
||||
is not an event collection, so map it to the events URL first
|
||||
(see ``_google_caldav_events_url``); other servers' URLs are used as-is.
|
||||
"""
|
||||
target = _google_caldav_events_url(url) or url
|
||||
return client.calendar(url=target)
|
||||
|
||||
|
||||
def _sync_blocking(owner: str, url: str, username: str, password: str) -> dict:
|
||||
"""The actual sync — synchronous, intended to run in a threadpool.
|
||||
Returns counts: {calendars, events, deleted, errors}."""
|
||||
@@ -192,14 +238,14 @@ def _sync_blocking(owner: str, url: str, username: str, password: str) -> dict:
|
||||
except Exception as e:
|
||||
logger.info(f"CalDAV principal discovery failed, trying URL as calendar: {e}")
|
||||
try:
|
||||
calendars = [client.calendar(url=url)]
|
||||
calendars = [_open_url_as_calendar(client, url)]
|
||||
except Exception as e2:
|
||||
result["errors"].append(f"Could not open URL as calendar: {e2}")
|
||||
return result
|
||||
|
||||
if not calendars:
|
||||
try:
|
||||
calendars = [client.calendar(url=url)]
|
||||
calendars = [_open_url_as_calendar(client, url)]
|
||||
except Exception as e:
|
||||
result["errors"].append(f"No calendars and URL fallback failed: {e}")
|
||||
return result
|
||||
|
||||
Reference in New Issue
Block a user