itchyny / calendar.vim

A calendar application for Vim
MIT License
1.95k stars 71 forks source link

support json authorization for gcal with gcsa #219

Closed JJJHolscher closed 8 months ago

JJJHolscher commented 1 year ago

Many of my gcal tools use Python's gcsa library. I like this library since it accepts the json file one downloads from google cloud, and stores the token in a pickle file. Gcsa takes care of opening the login page and refreshing the token. Also I can have multiple programs share the token so I only need to authenticate once.

Calendar.vim has its own authentication workflow which has the upsides of less dependencies and staying within vimscript. But it regularly bugs out for me and requires me to create a .vim file out of the json and occasionally I need to manually delete the cache.

I would like calendar.vim to be able to work with the json file. Preferably by using gcsa such that I can share the pickle file. Gcsa has been very reliable for me throughout my 2-year usage.

JJJHolscher commented 1 year ago

I got it to work by editing the following functions in autoload/calendar/google/client.vim

function! calendar#google#client#access_token() abort
  let cache = s:cache.get('access_token')
  if type(cache) != type({}) || type(cache) == type({}) && !has_key(cache, 'access_token')
    call calendar#google#client#initialize_access_token()
    let cache = s:cache.get('access_token')
    if type(cache) != type({}) || type(cache) == type({}) && !has_key(cache, 'access_token')
      return 1
    endif
    let content = cache
  else
    let content = cache
  endif
  return content.access_token
endfunction

let s:server_py_path = expand('<sfile>:p:h') . '/server.py'
function! calendar#google#client#initialize_access_token() abort
  if !executable('python3')
    call calendar#echo#error('Python 3 is required.')
    return
  endif
  let content = json_decode(system("python /path/to/gcal.py"))
  if calendar#google#client#access_token_response(content)
    return
  endif
  let g:calendar_google_event_downloading_list = 0
  let g:calendar_google_event_download = 3
  silent! let b:calendar.event._updated = 3
endfunction

function! calendar#google#client#refresh_token() abort
  let cache = s:cache.get('refresh_token')
  if type(cache) == type({}) && has_key(cache, 'refresh_token') && type(cache.refresh_token) == type('')
    let content = json_decode(system("python /path/to/gcal.py"))
    if calendar#google#client#access_token_response(content)
      return 1
    endif
    return content.access_token
  else
    return 1
  endif
endfunction

function! calendar#google#client#access_token_response(content) abort
  if !has_key(a:content, 'token')
    call calendar#echo#error_message('google_access_token_fail')
    return 1
  else
    let a:content.access_token = a:content.token
    call s:cache.save('access_token', a:content)
    if has_key(a:content, 'refresh_token') && type(a:content.refresh_token) == type('')
      call s:cache.save('refresh_token', { 'refresh_token': a:content.refresh_token })
    endif
  endif
endfunction

gcal.py (install gcsa with pip install gcsa)

import os
import sys
from gcsa.google_calendar import GoogleCalendar

with open(os.devnull, 'w') as f:
  # supress the print message that requests to authenticate in the browser
  # otherwise the output is not valid json that calendar.vim can interpret
  sys.stdout = f
  cred = GoogleCalendar(credentials_path="/home/user/gcal-credentials.json").credentials
sys.stdout = sys.__stdout__
print(cred.to_json())

gcsa will refresh the token upon calling this script and also pops up the authentication window in your browser if the token is not present or expired.

I'll open a PR if you think this adds, though I do think this method should replace the existing authentication method instead of being an alternative option. Also some of the other code probably became redundant with my fix, but I lack the knowledge about the code base as to efficiently prune the redundancies.

JJJHolscher commented 1 year ago

gcsa does not provide tasks, for that we'd need to use the google.oauth2 lib instead. Now I re-use the code from gcsa's authentication.py (instead of the above gcal.py) which, after adding the tasks scope, does work with tasks.

JJJHolscher commented 8 months ago

I stopped using this plugin.