gcal-service 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/gcal-service.js +188 -0
  2. package/package.json +40 -0
@@ -0,0 +1,188 @@
1
+ // just-gcal.js
2
+ import { google } from 'googleapis';
3
+ import fs from 'node:fs/promises';
4
+ import path from 'node:path';
5
+
6
+ /**
7
+ * Simple Google Calendar client using Service Account (read-only by default)
8
+ */
9
+ class JustGcal {
10
+ #calendar = null;
11
+ #initialized = false;
12
+
13
+ constructor({
14
+ keyFile = path.join(process.cwd(), 'service-account.json'),
15
+ scopes = ['https://www.googleapis.com/auth/calendar.readonly'],
16
+ calendarId = 'primary',
17
+ } = {}) {
18
+ this.keyFile = keyFile;
19
+ this.scopes = scopes;
20
+ this.calendarId = calendarId;
21
+ }
22
+
23
+ async initialize() {
24
+ if (this.#initialized) return;
25
+
26
+ try {
27
+ const keyContent = await fs.readFile(this.keyFile, 'utf8');
28
+ const credentials = JSON.parse(keyContent);
29
+
30
+ const auth = new google.auth.GoogleAuth({
31
+ credentials,
32
+ scopes: this.scopes,
33
+ });
34
+
35
+ const client = await auth.getClient();
36
+ this.#calendar = google.calendar({ version: 'v3', auth: client });
37
+
38
+ this.#initialized = true;
39
+ } catch (err) {
40
+ throw new Error(`JustGcal init failed: ${err.message}`);
41
+ }
42
+ }
43
+
44
+ get calendar() {
45
+ if (!this.#initialized) {
46
+ throw new Error('JustGcal not initialized. Call .initialize() or use a method first.');
47
+ }
48
+ return this.#calendar;
49
+ }
50
+
51
+ /**
52
+ * Fetch upcoming events (from now onward)
53
+ * @param {Object} options
54
+ * @param {number} [options.max=50]
55
+ * @param {Date|string} [options.timeMin] - defaults to now
56
+ * @param {Date|string} [options.timeMax]
57
+ * @param {boolean} [options.singleEvents=true]
58
+ * @param {string} [options.orderBy='startTime']
59
+ */
60
+ async listUpcoming({
61
+ max = 10,
62
+ timeMin = new Date(),
63
+ timeMax,
64
+ singleEvents = true,
65
+ orderBy = 'startTime',
66
+ } = {}) {
67
+ await this.initialize();
68
+
69
+ const params = {
70
+ calendarId: this.calendarId,
71
+ timeMin: timeMin instanceof Date ? timeMin.toISOString() : timeMin,
72
+ maxResults: max,
73
+ singleEvents,
74
+ orderBy,
75
+ };
76
+
77
+ if (timeMax) params.timeMax = timeMax instanceof Date ? timeMax.toISOString() : timeMax;
78
+
79
+ const res = await this.#calendar.events.list(params);
80
+ return res.data.items || [];
81
+ }
82
+
83
+ async printUpcoming(max = 10) {
84
+ try {
85
+ const events = await this.listUpcoming({ max });
86
+ if (events.length === 0) {
87
+ console.log('No upcoming events.');
88
+ return;
89
+ }
90
+ console.log(`\nNext ${events.length} event${events.length === 1 ? '' : 's'}:`);
91
+ events.forEach((e, i) => {
92
+ const start = e.start?.dateTime || e.start?.date || '—';
93
+ const title = e.summary || '(untitled)';
94
+ console.log(`${i + 1}. ${start} ${title}`);
95
+ });
96
+ } catch (err) {
97
+ console.error('Error:', err.message);
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Fetch all events that occur (at least partially) on a given day
103
+ * @param {Date|string} date - Date to query (can be Date object or ISO string YYYY-MM-DD)
104
+ * @param {Object} [options]
105
+ * @param {number} [options.max=100] - safety limit
106
+ * @returns {Promise<Array<import('googleapis').calendar_v3.Schema$Event>>}
107
+ */
108
+ async listEventsForDay(date, { max = 50 } = {}) {
109
+ await this.initialize();
110
+
111
+ const target = typeof date === 'string' ? new Date(date) : date;
112
+ if (isNaN(target)) throw new Error('Invalid date format');
113
+
114
+ // Start of day (00:00:00 local time)
115
+ const startOfDay = new Date(target);
116
+ startOfDay.setHours(0, 0, 0, 0);
117
+
118
+ // End of day (23:59:59.999 local time)
119
+ const endOfDay = new Date(startOfDay);
120
+ endOfDay.setDate(endOfDay.getDate() + 1);
121
+ endOfDay.setMilliseconds(-1);
122
+
123
+ const res = await this.#calendar.events.list({
124
+ calendarId: this.calendarId,
125
+ timeMin: startOfDay.toISOString(),
126
+ timeMax: endOfDay.toISOString(),
127
+ singleEvents: true,
128
+ orderBy: 'startTime',
129
+ maxResults: max,
130
+ });
131
+
132
+ return res.data.items || [];
133
+ }
134
+
135
+ /**
136
+ * Print events for a specific day in a readable format
137
+ * @param {Date|string} date
138
+ */
139
+ async printEventsForDay(date) {
140
+ try {
141
+ const events = await this.listEventsForDay(date);
142
+
143
+ const dateStr = (typeof date === 'string' ? new Date(date) : date)
144
+ .toLocaleDateString('en-SG', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' });
145
+
146
+ if (events.length === 0) {
147
+ console.log(`\nNo events on ${dateStr}`);
148
+ return;
149
+ }
150
+
151
+ console.log(`\nEvents on ${dateStr} (${events.length}):`);
152
+ events.forEach((e, i) => {
153
+ let timeStr = 'All day';
154
+
155
+ if (e.start.dateTime) {
156
+ const start = new Date(e.start.dateTime);
157
+ const end = e.end.dateTime ? new Date(e.end.dateTime) : null;
158
+ timeStr = start.toLocaleTimeString('en-SG', { hour: 'numeric', minute: '2-digit', hour12: true });
159
+ if (end) {
160
+ timeStr += ` – ${end.toLocaleTimeString('en-SG', { hour: 'numeric', minute: '2-digit', hour12: true })}`;
161
+ }
162
+ }
163
+
164
+ const title = e.summary || '(untitled)';
165
+ console.log(`${i + 1}. ${timeStr} ${title}`);
166
+ });
167
+ } catch (err) {
168
+ console.error('Error fetching day events:', err.message);
169
+ }
170
+ }
171
+
172
+ static async quickShow(calendarId = 'primary', max = 50) {
173
+ const gcal = new JustGcal({ calendarId });
174
+ await gcal.printUpcoming(max);
175
+ }
176
+
177
+ /**
178
+ * Quick helper: show events for one day
179
+ * @param {Date|string} date
180
+ * @param {string} [calendarId='primary']
181
+ */
182
+ static async quickShowDay(date, calendarId = 'primary') {
183
+ const gcal = new JustGcal({ calendarId });
184
+ await gcal.printEventsForDay(date);
185
+ }
186
+ }
187
+
188
+ export { JustGcal };
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "gcal-service",
3
+ "version": "1.1.0",
4
+ "description": "Minimal Google Calendar client using service account – list upcoming events, day-specific events, read-only by default",
5
+ "main": "./gcal-service.js",
6
+ "type": "module",
7
+ "exports": {
8
+ "default": "./gcal-service.js"
9
+ },
10
+ "files": [
11
+ "gcal-service.js",
12
+ "README.md",
13
+ "LICENSE"
14
+ ],
15
+ "keywords": [
16
+ "google-calendar",
17
+ "gcal",
18
+ "calendar",
19
+ "events",
20
+ "googleapis",
21
+ "service-account",
22
+ "gcal-service"
23
+ ],
24
+ "author": "Dan <littlejustnode@gmail.com>[](https://github.com/littlejustnode)",
25
+ "license": "MIT",
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "git+https://github.com/littlejustnode/gcal-service.git"
29
+ },
30
+ "bugs": {
31
+ "url": "https://github.com/littlejustnode/gcal-service/issues"
32
+ },
33
+ "homepage": "https://github.com/littlejustnode/gcal-service#readme",
34
+ "dependencies": {
35
+ "googleapis": "^144.0.0"
36
+ },
37
+ "engines": {
38
+ "node": ">=18.0.0"
39
+ }
40
+ }