206 lines
5.2 KiB
Lua
206 lines
5.2 KiB
Lua
local config = require('pending.config')
|
|
local oauth = require('pending.sync.oauth')
|
|
|
|
local M = {}
|
|
|
|
M.name = 'gcal'
|
|
|
|
local BASE_URL = 'https://www.googleapis.com/calendar/v3'
|
|
local SCOPE = 'https://www.googleapis.com/auth/calendar'
|
|
|
|
local client = oauth.new({
|
|
name = 'gcal',
|
|
scope = SCOPE,
|
|
port = 18392,
|
|
config_key = 'gcal',
|
|
})
|
|
|
|
---@return string? calendar_id
|
|
---@return string? err
|
|
local function find_or_create_calendar(access_token)
|
|
local cfg = config.get()
|
|
local gc = (cfg.sync and cfg.sync.gcal) or {}
|
|
local cal_name = gc.calendar or 'Pendings'
|
|
|
|
local data, err = oauth.curl_request(
|
|
'GET',
|
|
BASE_URL .. '/users/me/calendarList',
|
|
oauth.auth_headers(access_token)
|
|
)
|
|
if err then
|
|
return nil, err
|
|
end
|
|
|
|
for _, item in ipairs(data and data.items or {}) do
|
|
if item.summary == cal_name then
|
|
return item.id, nil
|
|
end
|
|
end
|
|
|
|
local body = vim.json.encode({ summary = cal_name })
|
|
local created, create_err =
|
|
oauth.curl_request('POST', BASE_URL .. '/calendars', oauth.auth_headers(access_token), body)
|
|
if create_err then
|
|
return nil, create_err
|
|
end
|
|
|
|
return created and created.id, nil
|
|
end
|
|
|
|
---@param date_str string
|
|
---@return string
|
|
local function next_day(date_str)
|
|
local y, m, d = date_str:match('^(%d%d%d%d)-(%d%d)-(%d%d)$')
|
|
local t = os.time({ year = tonumber(y) or 0, month = tonumber(m) or 0, day = tonumber(d) or 0 })
|
|
+ 86400
|
|
return os.date('%Y-%m-%d', t) --[[@as string]]
|
|
end
|
|
|
|
---@param access_token string
|
|
---@param calendar_id string
|
|
---@param task pending.Task
|
|
---@return string? event_id
|
|
---@return string? err
|
|
local function create_event(access_token, calendar_id, task)
|
|
local event = {
|
|
summary = task.description,
|
|
start = { date = task.due },
|
|
['end'] = { date = next_day(task.due or '') },
|
|
transparency = 'transparent',
|
|
extendedProperties = {
|
|
private = { taskId = tostring(task.id) },
|
|
},
|
|
}
|
|
local data, err = oauth.curl_request(
|
|
'POST',
|
|
BASE_URL .. '/calendars/' .. oauth.url_encode(calendar_id) .. '/events',
|
|
oauth.auth_headers(access_token),
|
|
vim.json.encode(event)
|
|
)
|
|
if err then
|
|
return nil, err
|
|
end
|
|
return data and data.id, nil
|
|
end
|
|
|
|
---@param access_token string
|
|
---@param calendar_id string
|
|
---@param event_id string
|
|
---@param task pending.Task
|
|
---@return string? err
|
|
local function update_event(access_token, calendar_id, event_id, task)
|
|
local event = {
|
|
summary = task.description,
|
|
start = { date = task.due },
|
|
['end'] = { date = next_day(task.due or '') },
|
|
}
|
|
local _, err = oauth.curl_request(
|
|
'PATCH',
|
|
BASE_URL
|
|
.. '/calendars/'
|
|
.. oauth.url_encode(calendar_id)
|
|
.. '/events/'
|
|
.. oauth.url_encode(event_id),
|
|
oauth.auth_headers(access_token),
|
|
vim.json.encode(event)
|
|
)
|
|
return err
|
|
end
|
|
|
|
---@param access_token string
|
|
---@param calendar_id string
|
|
---@param event_id string
|
|
---@return string? err
|
|
local function delete_event(access_token, calendar_id, event_id)
|
|
local _, err = oauth.curl_request(
|
|
'DELETE',
|
|
BASE_URL
|
|
.. '/calendars/'
|
|
.. oauth.url_encode(calendar_id)
|
|
.. '/events/'
|
|
.. oauth.url_encode(event_id),
|
|
oauth.auth_headers(access_token)
|
|
)
|
|
return err
|
|
end
|
|
|
|
function M.auth()
|
|
client:auth()
|
|
end
|
|
|
|
function M.sync()
|
|
local access_token = client:get_access_token()
|
|
if not access_token then
|
|
return
|
|
end
|
|
|
|
local calendar_id, err = find_or_create_calendar(access_token)
|
|
if err or not calendar_id then
|
|
vim.notify('pending.nvim: ' .. (err or 'calendar not found'), vim.log.levels.ERROR)
|
|
return
|
|
end
|
|
|
|
local tasks = require('pending').store():tasks()
|
|
local created, updated, deleted = 0, 0, 0
|
|
|
|
for _, task in ipairs(tasks) do
|
|
local extra = task._extra or {}
|
|
local event_id = extra['_gcal_event_id'] --[[@as string?]]
|
|
|
|
local should_delete = event_id ~= nil
|
|
and (
|
|
task.status == 'done'
|
|
or task.status == 'deleted'
|
|
or (task.status == 'pending' and not task.due)
|
|
)
|
|
|
|
if should_delete and event_id then
|
|
local del_err = delete_event(access_token, calendar_id, event_id) --[[@as string]]
|
|
if not del_err then
|
|
extra['_gcal_event_id'] = nil
|
|
if next(extra) == nil then
|
|
task._extra = nil
|
|
else
|
|
task._extra = extra
|
|
end
|
|
task.modified = os.date('!%Y-%m-%dT%H:%M:%SZ') --[[@as string]]
|
|
deleted = deleted + 1
|
|
end
|
|
elseif task.status == 'pending' and task.due then
|
|
if event_id then
|
|
local upd_err = update_event(access_token, calendar_id, event_id, task)
|
|
if not upd_err then
|
|
updated = updated + 1
|
|
end
|
|
else
|
|
local new_id, create_err = create_event(access_token, calendar_id, task)
|
|
if not create_err and new_id then
|
|
if not task._extra then
|
|
task._extra = {}
|
|
end
|
|
task._extra['_gcal_event_id'] = new_id
|
|
task.modified = os.date('!%Y-%m-%dT%H:%M:%SZ') --[[@as string]]
|
|
created = created + 1
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
require('pending').store():save()
|
|
require('pending')._recompute_counts()
|
|
vim.notify(
|
|
string.format(
|
|
'pending.nvim: Synced to Google Calendar (created: %d, updated: %d, deleted: %d)',
|
|
created,
|
|
updated,
|
|
deleted
|
|
)
|
|
)
|
|
end
|
|
|
|
---@return nil
|
|
function M.health()
|
|
oauth.health(M.name)
|
|
end
|
|
|
|
return M
|