the oauth2 emacs lib *claims* it does this for us, but it demonstrably doesn't. Change-Id: I6495ac30799bb3d3fd7406cec5139602c311d22a Reviewed-on: https://cl.tvl.fyi/c/depot/+/1977 Reviewed-by: glittershark <grfn@gws.fyi> Tested-by: BuildkiteCI
		
			
				
	
	
		
			181 lines
		
	
	
	
		
			5.2 KiB
		
	
	
	
		
			EmacsLisp
		
	
	
	
	
	
			
		
		
	
	
			181 lines
		
	
	
	
		
			5.2 KiB
		
	
	
	
		
			EmacsLisp
		
	
	
	
	
	
| ;;; ~/.doom.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"
 | |
|   )
 | |
| 
 | |
|  )
 |