about summary refs log tree commit diff
path: root/users/grfn/emacs.d/org-gcal.el
;;; -*- lexical-binding: t; -*-

(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)))
    (oauth2-refresh-access auth-token)
    (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
             (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:
${startTime}--${endTime}
:PROPERTIES:
${location-prop}
:EVENT: ${htmlLink}
:END:

${description}"
         (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)
 )

(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"
  )

 )