- New server/google_calendar.mjs wrapping googleapis v3 with auto token refresh - Four new actions: calendar-list-events (auto-accept), calendar-create/update/delete-event (queue) - bin/ccc-gcal-auth.mjs one-time OAuth2 consent flow helper - config.example.json updated with google_calendar block - server/config.mjs, index.mjs wired up following the same pattern as SMTP/mailer - Bump version to 1.2.0 - Add CLAUDE.md and claude-info/ with architecture reference, feature plans, and contributing checklist Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
77 lines
2.4 KiB
JavaScript
77 lines
2.4 KiB
JavaScript
import { readFileSync, writeFileSync } from 'node:fs';
|
|
import { google } from 'googleapis';
|
|
|
|
// Returns a calendar client wrapping the Google Calendar API v3.
|
|
// cfg = { credentials_path, token_path }
|
|
export function create_calendar_client({ credentials_path, token_path }) {
|
|
const credentials = JSON.parse(readFileSync(credentials_path, 'utf8'));
|
|
const { client_id, client_secret, redirect_uris } = credentials.installed ?? credentials.web;
|
|
|
|
const oauth2_client = new google.auth.OAuth2(client_id, client_secret, redirect_uris[0]);
|
|
|
|
const token = JSON.parse(readFileSync(token_path, 'utf8'));
|
|
oauth2_client.setCredentials(token);
|
|
|
|
// Persist refreshed tokens automatically
|
|
oauth2_client.on('tokens', (new_tokens) => {
|
|
let current = {};
|
|
try {
|
|
current = JSON.parse(readFileSync(token_path, 'utf8'));
|
|
} catch (_) {}
|
|
const merged = { ...current, ...new_tokens };
|
|
writeFileSync(token_path, JSON.stringify(merged, null, '\t') + '\n', 'utf8');
|
|
});
|
|
|
|
const cal = google.calendar({ version: 'v3', auth: oauth2_client });
|
|
|
|
async function list_events({ calendar_id = 'primary', time_min, time_max, max_results = 10 } = {}) {
|
|
const res = await cal.events.list({
|
|
calendarId: calendar_id,
|
|
timeMin: time_min,
|
|
timeMax: time_max,
|
|
maxResults: max_results,
|
|
singleEvents: true,
|
|
orderBy: 'startTime',
|
|
});
|
|
return res.data.items ?? [];
|
|
}
|
|
|
|
async function create_event({ calendar_id = 'primary', summary, start, end, description }) {
|
|
const res = await cal.events.insert({
|
|
calendarId: calendar_id,
|
|
requestBody: {
|
|
summary,
|
|
description,
|
|
start: { dateTime: start },
|
|
end: { dateTime: end },
|
|
},
|
|
});
|
|
return res.data;
|
|
}
|
|
|
|
async function update_event({ calendar_id = 'primary', event_id, summary, start, end, description }) {
|
|
const patch = {};
|
|
if (summary !== undefined) { patch.summary = summary; }
|
|
if (description !== undefined) { patch.description = description; }
|
|
if (start !== undefined) { patch.start = { dateTime: start }; }
|
|
if (end !== undefined) { patch.end = { dateTime: end }; }
|
|
|
|
const res = await cal.events.patch({
|
|
calendarId: calendar_id,
|
|
eventId: event_id,
|
|
requestBody: patch,
|
|
});
|
|
return res.data;
|
|
}
|
|
|
|
async function delete_event({ calendar_id = 'primary', event_id }) {
|
|
await cal.events.delete({
|
|
calendarId: calendar_id,
|
|
eventId: event_id,
|
|
});
|
|
return { deleted: true, event_id };
|
|
}
|
|
|
|
return { list_events, create_event, update_event, delete_event };
|
|
}
|