list_calendars only kept PROPFIND responses whose resourcetype was
<c:calendar>, so external subscriptions (webcal/ICS feeds) — exposed by
Nextcloud as <cs:subscribed> collections — were silently dropped and never
appeared in nc_calendar_list_calendars (issue #830).
- Construct the calendar DAV client with `X-NC-CalDAV-Webcal-Caching: On` so
Nextcloud exposes subscriptions as queryable CachedSubscription calendars,
making their events readable through the existing event/search tools (the
same mechanism Evolution/KDE desktop clients use).
- In list_calendars, override that header to "Off" so subscriptions are
returned as cs:subscribed collections with their cs:source href, then parse
responses whose resourcetype is c:calendar OR cs:subscribed. Subscriptions
are reported with read_only=True and their source feed URL; their color is
read from the Apple iCal namespace as a fallback.
- Send the custom PROPFIND markup via `body=` instead of `props=`: under
caldav 3.x `props=` expects a list of property names and discards a raw XML
string, producing an empty <prop/>. This also restores display_name,
description and color which were previously falling back to defaults.
- Add `read_only` and `source` fields to the Calendar model.
Adds unit tests for subscription parsing, the body/header wiring, and the
webcal-caching header; extends the integration test to assert the new fields.
When Nextcloud stores CalDAV objects, the server-side filename may differ
from the VTODO/VEVENT UID. The caldav fork constructed object URLs from
the UID instead of the actual <d:href> from REPORT responses, causing
list_todos to return wrong hrefs, delete_todo to silently no-op, and
update_todo to fail.
Upstream caldav v3.0.1 fixes this in _async_request_report_build_resultlist
by passing url=self.url.join(url) when constructing result objects.
Key changes:
- Replace caldav fork with upstream caldav>=3.0.1,<4.0
- Update imports to caldav.aio module
- Add _maybe_await() helper for v3's dual-mode methods that return
either objects or coroutines depending on async context
- Add _async_object_by_uid() to work around upstream's get_object_by_uid
not being async-aware (it iterates a coroutine synchronously)
- Adapt save_event/save_todo (no longer return tuples)
- Pass url=calendar.url.join(href) in _search_events_by_date
- Pass include_completed=True in list_todos to match previous behavior
- Add integration test for filename != UID scenario
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
_merge_ical_properties() only handled a subset of event fields, silently
dropping categories, recurrence_rule, attendees, and reminder_minutes
during updates. These fields were fully supported by _create_ical_event()
and accepted by the MCP tool, but never applied.
Closes#544
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
PR #539 fixed date-range filtering so events outside the queried range
are excluded. However, recurring events still returned the master event
with its original DTSTART instead of expanded occurrences.
Add <C:expand> element to CalDAV REPORT requests (RFC 4791 §9.6.5) when
both date bounds are provided, so the server returns one VEVENT per
occurrence with the correct DTSTART. Refactor VEVENT parsing into a
shared helper and add _parse_all_ical_events() to handle multi-VEVENT
responses from expanded results.
Closes#538
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
get_calendar_events() accepted start/end datetime parameters but called
calendar.events() which fetches all events, silently discarding the
date filters. This caused nc_calendar_list_events and
nc_calendar_get_upcoming_events to return the entire calendar history.
Add _search_events_by_date() helper that builds a CalDAV REPORT query
with a <time-range> filter (RFC 4791 §9.9) for server-side filtering.
Falls back to calendar.events() when no dates are given.
Closes#538
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>