A simple integration of org capture with Google Calendar

Keywords: org mode org capture Google Calendar Elisp Bash scripting

In this solution I only consider this use case: the user capture an appointment using org capture and the appointment is automatically uploaded to Google Calendar. Therefore, if the event is created in Google Calendar then Emacs is not updated, or if the event is updated or deleted in one them, the other will not be modified.

I just want to capture an appointment from Emacs and have the notification in the mobile phone. To quickly capture things I use org capture using different templates (see the related post ../post-aviso-citas/post.html). The template for appointments creates a new entry at the top of the file citas.org.

Org capture allows the execution of a hook (an elisp function) when the capture ends, so it is possible from that function to run a script that obtains the data from the first appointment. This data can be parsed to obtain: the title, the date, and the start time of the appointment. These tokens can be passed to a CLI that communicates with Google Calendar, for instance gcalcli.

The first thing is to install gcalcli and use Google developer console to create a new Client ID OAuth 2.0 with permission to make requests to Google Calendar. This process is described in the gcalcli page.

The generated Client ID and the Secret are needed to make requests. This info can be stored in a plain text file but I don't like that solution.

So I created a file gcal with the following content (replace with your Client ID and Secret):

--client-id=xxxxxxxxxxxxxxx.apps.googleusercontent.com
  --client-secret=xxxxxxxxxxxxxxxxx

then I encrypted using gpg

gpg -c gcal

this will ask you for a password. Finally remove the original (plain text file)

rm gcal

When I create a new appointment, the capture template adds this info to the file:

 ** CITA Reunión
 :LOGBOOK:
 Created: [2021-01-30 sáb 19:42]
 :END:
 <2021-01-30 sáb 20:00>

So, in my case, the info about the appointment starts with CITA at line \(l\) and ends at line \(l+4\).

The next step is to write a script to get the appointment's info from the file, parse that info, and use gcalcli to add the event to Google Calendar (you need to customize this script):

#!/bin/bas
# Get data from appointments file and create a new event in Google Calendar

#Put here the location of your appointment file:
appts=PATH
# This is the text that I use in the capture template to signal that is an appointment
appts_keyword=CITA
# Put here the path of the file where the Client ID and secret are encrypted
gcalid=PATH
# Put here the name of your Google Calendar
calendar=NAME

# Get the line where the first appointment starts
lineS=$(grep -n $appt_keyword $appts | head -n1 | cut -d':' -f1)
# Calculate the last line (in my case I have to add 4)
lineE=$(echo "$lineS+4" | bc -l)

# Get the info
data=$(awk "NR >= $lineS && NR <= $lineE" $PATH)
# Parse the info to obtain the date and time
when=$(echo "$data" | egrep -e "<[1-9].*>" | tr -d '\>' | tr -d '\<' )

# Get the title
title="$(echo "$data" | awk -F"$appts_keyword " '{print $2}')"
# Get the date
date="$(echo "$when" | cut -d' ' -f1)"
# Get the time
start="$(echo "$when" | cut -d' ' -f3)"

# Reminder time before the appointment
reminder=20

# If start is empty I put 9:30
if [ -z "$start" ]; then
 start="9:30"
fi

# If date is not empty I create the event
if [ -n "$date" ]; then
 # Just to debug
 echo "Uploading event to Google Calendar"
 echo "$title"
 echo "$date"
 echo "$start"
 echo "$duration"
 echo "$reminder"

 # Call gcalcli with the information extracted
 # Also decrypt the file with the identity and secret
 gcalcli $(gpg -d "gcalid") --calendar $calendar add --title "$title" --when "$date $start" --duration '10' --reminder "$reminder" --noprompt
fi

The first time you run this script, the browser opens and Google ask for the user's authorization to the application making the request.

The final step is to call this script when a new appointment is captured. In your Emacs configuration file add:

;; Customize this variable
(setq gcalScript "Put here the path to the previous script")

;; Org capture integration with Google Calendar
(defun gcal ()
  (let (
        (desc (plist-get org-capture-plist :description)))
    ;; I use Cita in the org-capture template
    (if (string= desc "Cita")
        (start-process "gcal" nil gcalScript))
    ))

(add-hook 'org-capture-after-finalize-hook 'gcal)

So when I launch org capture and select c (my key to capture a new appointment):

capture.png

provide the information:

appointment.png

and store the appointment with C-c C-c, the hook is executed and I can see the appointment in my phone:

appointment-in-phone.png

Goto index

Date: 30/01/2021

Author: Juan GutiƩrrez Aguado

Emacs 27.1 (Org mode 9.4.4)

Validate