2021-04-11 23:53:27 +02:00
|
|
|
;;; -*- lexical-binding: t; -*-
|
2020-06-15 17:56:12 +02:00
|
|
|
|
|
|
|
(require 'aio)
|
|
|
|
(require 'parse-time)
|
|
|
|
|
|
|
|
(setq-local lexical-binding t)
|
|
|
|
(setq plstore-cache-passphrase-for-symmetric-encryption t)
|
|
|
|
|
|
|
|
(defvar gcal-client-id)
|
|
|
|
(defvar gcal-client-secret)
|
|
|
|
|
|
|
|
(defvar google-calendar-readonly-scope
|
|
|
|
"https://www.googleapis.com/auth/calendar.readonly")
|
|
|
|
|
|
|
|
(defvar events-file "/home/grfn/notes/events.org")
|
|
|
|
|
|
|
|
(defun google--get-token (scope client-id client-secret)
|
|
|
|
(oauth2-auth-and-store
|
|
|
|
"https://accounts.google.com/o/oauth2/v2/auth"
|
|
|
|
"https://oauth2.googleapis.com/token"
|
|
|
|
scope
|
|
|
|
client-id
|
|
|
|
client-secret))
|
|
|
|
|
|
|
|
(cl-defun google--request (url &key method params scope)
|
|
|
|
(let ((p (aio-promise))
|
|
|
|
(auth-token (google--get-token scope gcal-client-id gcal-client-secret)))
|
2020-09-14 17:36:50 +02:00
|
|
|
(oauth2-refresh-access auth-token)
|
2020-06-15 17:56:12 +02:00
|
|
|
(oauth2-url-retrieve
|
|
|
|
auth-token
|
|
|
|
url
|
|
|
|
(lambda (&rest _)
|
|
|
|
(goto-char (point-min))
|
|
|
|
(re-search-forward "^$")
|
|
|
|
(let ((resp (json-parse-buffer :object-type 'alist)))
|
|
|
|
(aio-resolve p (lambda () resp))))
|
|
|
|
nil
|
|
|
|
(or method "GET")
|
|
|
|
params)
|
|
|
|
p))
|
|
|
|
|
|
|
|
(cl-defun list-events (&key min-time max-time)
|
|
|
|
(google--request
|
|
|
|
(concat
|
|
|
|
"https://www.googleapis.com/calendar/v3/calendars/griffin@urbint.com/events"
|
|
|
|
"?timeMin=" (format-time-string "%Y-%m-%dT%T%z" min-time)
|
|
|
|
"&timeMax=" (format-time-string "%Y-%m-%dT%T%z" max-time))
|
|
|
|
:scope google-calendar-readonly-scope))
|
|
|
|
|
|
|
|
|
|
|
|
(defun last-week-events ()
|
|
|
|
(list-events :min-time (time-subtract
|
|
|
|
(current-time)
|
|
|
|
(seconds-to-time
|
|
|
|
(* 60 60 24 7)))
|
|
|
|
:max-time (current-time)))
|
|
|
|
|
|
|
|
(defun next-week-events ()
|
|
|
|
(list-events :min-time (current-time)
|
|
|
|
:max-time (time-add
|
|
|
|
(current-time)
|
|
|
|
(seconds-to-time
|
|
|
|
(* 60 60 24 7)))))
|
|
|
|
|
|
|
|
(defun attending-event? (event)
|
|
|
|
(let* ((attendees (append (alist-get 'attendees event) nil))
|
|
|
|
(self (--find (alist-get 'self it) attendees)))
|
|
|
|
(equal "accepted" (alist-get 'responseStatus self))))
|
|
|
|
|
|
|
|
(defun event->org-headline (event level)
|
|
|
|
(cl-flet ((make-time
|
2020-06-22 16:03:37 +02:00
|
|
|
(key)
|
|
|
|
(when-let ((raw-time (->> event (alist-get key) (alist-get 'dateTime))))
|
|
|
|
(format-time-string
|
|
|
|
(org-time-stamp-format t)
|
|
|
|
(parse-iso8601-time-string raw-time)))))
|
|
|
|
(if-let ((start-time (make-time 'start))
|
|
|
|
(end-time (make-time 'end)))
|
|
|
|
(s-format
|
|
|
|
"${headline} [[${htmlLink}][${summary}]] :event:
|
2020-06-15 17:56:12 +02:00
|
|
|
${startTime}--${endTime}
|
|
|
|
:PROPERTIES:
|
2020-06-22 16:03:37 +02:00
|
|
|
${location-prop}
|
2020-06-15 17:56:12 +02:00
|
|
|
:EVENT: ${htmlLink}
|
|
|
|
:END:
|
|
|
|
|
|
|
|
${description}"
|
2020-06-22 16:03:37 +02:00
|
|
|
(function
|
|
|
|
(lambda (k m)
|
|
|
|
(or (alist-get (intern k) m)
|
|
|
|
(format "key not found: %s" k))))
|
|
|
|
(append
|
|
|
|
event
|
|
|
|
`((headline . ,(make-string level ?*))
|
|
|
|
(startTime . ,start-time)
|
|
|
|
(endTime . ,end-time)
|
|
|
|
(location-prop
|
|
|
|
. ,(if-let ((location (alist-get 'location event)))
|
|
|
|
(s-lex-format ":LOCATION: ${location}")
|
|
|
|
"")))))
|
|
|
|
"")))
|
|
|
|
|
|
|
|
(comment
|
|
|
|
(alist-get 'foo nil)
|
|
|
|
)
|
2020-06-15 17:56:12 +02:00
|
|
|
|
|
|
|
(defun write-events (events)
|
|
|
|
(with-current-buffer (find-file-noselect events-file)
|
|
|
|
(save-mark-and-excursion
|
|
|
|
(save-restriction
|
|
|
|
(widen)
|
|
|
|
(erase-buffer)
|
|
|
|
(goto-char (point-min))
|
|
|
|
(insert "#+TITLE: Events")
|
|
|
|
(newline) (newline)
|
|
|
|
(prog1
|
|
|
|
(loop for event in (append events nil)
|
|
|
|
when (attending-event? event)
|
|
|
|
do
|
|
|
|
(insert (event->org-headline event 1))
|
|
|
|
(newline)
|
|
|
|
sum 1)
|
|
|
|
(org-align-tags t))))))
|
|
|
|
|
|
|
|
(defun +grfn/sync-events ()
|
|
|
|
(interactive)
|
|
|
|
(let* ((events (alist-get 'items (aio-wait-for (next-week-events))))
|
|
|
|
(num-written (write-events events)))
|
|
|
|
(message "Successfully wrote %d events" num-written)))
|
|
|
|
|
|
|
|
(comment
|
|
|
|
((kind . "calendar#event")
|
|
|
|
(etag . "\"3174776941020000\"")
|
|
|
|
(id . "SNIP")
|
|
|
|
(status . "confirmed")
|
|
|
|
(htmlLink . "https://www.google.com/calendar/event?eid=SNIP")
|
|
|
|
(created . "2020-04-01T13:30:09.000Z")
|
|
|
|
(updated . "2020-04-20T13:14:30.510Z")
|
|
|
|
(summary . "SNIP")
|
|
|
|
(description . "SNIP")
|
|
|
|
(location . "SNIP")
|
|
|
|
(creator
|
|
|
|
(email . "griffin@urbint.com")
|
|
|
|
(self . t))
|
|
|
|
(organizer
|
|
|
|
(email . "griffin@urbint.com")
|
|
|
|
(self . t))
|
|
|
|
(start
|
|
|
|
(dateTime . "2020-04-01T12:00:00-04:00")
|
|
|
|
(timeZone . "America/New_York"))
|
|
|
|
(end
|
|
|
|
(dateTime . "2020-04-01T12:30:00-04:00")
|
|
|
|
(timeZone . "America/New_York"))
|
|
|
|
(recurrence .
|
|
|
|
["RRULE:FREQ=WEEKLY;UNTIL=20200408T035959Z;BYDAY=WE"])
|
|
|
|
(iCalUID . "SNIP")
|
|
|
|
(sequence . 0)
|
|
|
|
(attendees .
|
|
|
|
[((email . "griffin@urbint.com")
|
|
|
|
(organizer . t)
|
|
|
|
(self . t)
|
|
|
|
(responseStatus . "accepted"))
|
|
|
|
((email . "SNIP")
|
|
|
|
(displayName . "SNIP")
|
|
|
|
(responseStatus . "needsAction"))])
|
|
|
|
(extendedProperties
|
|
|
|
(private
|
|
|
|
(origRecurringId . "309q48kc1dihsvbi13pnlimb5a"))
|
|
|
|
(shared
|
|
|
|
(origRecurringId . "309q48kc1dihsvbi13pnlimb5a")))
|
|
|
|
(reminders
|
|
|
|
(useDefault . t)))
|
|
|
|
|
|
|
|
(require 'icalendar)
|
|
|
|
|
|
|
|
(icalendar--convert-recurring-to-diary
|
|
|
|
nil
|
|
|
|
"RRULE:FREQ=WEEKLY;UNTIL=20200408T035959Z;BYDAY=WE"
|
|
|
|
)
|
|
|
|
|
|
|
|
)
|