diff options
| author | Craig Jennings <c@cjennings.net> | 2026-05-21 15:38:56 -0400 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-05-21 15:38:56 -0400 |
| commit | 7a42b8573674ab394ecd8e81b544055c91fc0d45 (patch) | |
| tree | b0a4d4415cd53cc611f2389ad246c74f3650a4ef /modules | |
| parent | 9dea49532d8fbe20b0fdb2291e857925bbad18c1 (diff) | |
| download | dotemacs-7a42b8573674ab394ecd8e81b544055c91fc0d45.tar.gz dotemacs-7a42b8573674ab394ecd8e81b544055c91fc0d45.zip | |
feat(calendar-sync): resolve .ics feed URLs from auth-source
A calendar's .ics feed URL is a secret token, so I'd rather not keep it in a plaintext config file. A calendar can now name a :secret-host, and calendar-sync--calendar-url looks the URL up in auth-source (~/.authinfo.gpg) at sync time. Inline :url still works and wins when both are set, so existing configs are unaffected.
I added 7 tests covering the explicit-url, string-secret, function-secret, precedence, and no-match paths, and switched the .example template to the :secret-host shape.
Diffstat (limited to 'modules')
| -rw-r--r-- | modules/calendar-sync.el | 32 |
1 files changed, 27 insertions, 5 deletions
diff --git a/modules/calendar-sync.el b/modules/calendar-sync.el index 2fb1df03d..9379b4270 100644 --- a/modules/calendar-sync.el +++ b/modules/calendar-sync.el @@ -71,6 +71,7 @@ (require 'cl-lib) (require 'subr-x) +(require 'auth-source) (require 'cj-org-text-lib) (defun calendar-sync--log-silently (format-string &rest args) @@ -92,8 +93,13 @@ Each calendar is a plist. Common keys: :file - Output file path for org format :fetcher - Fetch path: \\='ics (default) or \\='api -For the default \\='ics fetcher (Proton, plain .ics feeds): - :url - URL to fetch the .ics file from +For the default \\='ics fetcher (Proton, plain .ics feeds), give the feed +URL one of two ways: + :url - the feed URL inline (plaintext in this file) + :secret-host - an auth-source host whose secret holds the feed URL, + looked up in ~/.authinfo.gpg (encrypted at rest). Prefer + this: the .ics URL is itself a secret token. If both are + set, :url wins. For the \\='api fetcher (Google Calendar, sees per-occurrence response status so OOO auto-declines on recurring events can be filtered): @@ -1497,11 +1503,27 @@ calendar files do not block the interactive Emacs thread." (calendar-sync--sync-calendar-api calendar) (calendar-sync--sync-calendar-ics calendar))) +(defun calendar-sync--calendar-url (calendar) + "Return the .ics feed URL for CALENDAR, or nil if none is configured. +An explicit :url wins. Otherwise :secret-host names an auth-source host +whose stored secret is the URL (kept in auth-source because the .ics URL +is itself a token)." + (or (plist-get calendar :url) + (let ((host (plist-get calendar :secret-host))) + (when host + (let ((secret (plist-get (car (auth-source-search :host host :max 1)) + :secret))) + ;; auth-source's netrc backend returns the secret as a function + (cond ((functionp secret) (funcall secret)) + (secret secret))))))) + (defun calendar-sync--sync-calendar-ics (calendar) - "Sync a single CALENDAR from its :url .ics feed asynchronously. -CALENDAR is a plist with :name, :url, and :file keys." + "Sync a single CALENDAR from its .ics feed asynchronously. +CALENDAR is a plist with :name, :file, and a feed URL resolved by +`calendar-sync--calendar-url' (an explicit :url, or a :secret-host +looked up in auth-source)." (let ((name (plist-get calendar :name)) - (url (plist-get calendar :url)) + (url (calendar-sync--calendar-url calendar)) (file (plist-get calendar :file)) (fetch-start (float-time))) (calendar-sync--set-calendar-state name '(:status syncing)) |
