@hasna/connectors 1.3.22 → 1.3.23
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.
- package/bin/index.js +109 -59
- package/bin/mcp.js +236 -182
- package/bin/serve.js +77 -33
- package/connectors/connect-dropbox/src/api/bulk.ts +285 -0
- package/connectors/connect-dropbox/src/api/index.ts +4 -0
- package/connectors/connect-dropbox/src/cli/index.ts +151 -0
- package/connectors/connect-github/src/api/bulk.ts +328 -0
- package/connectors/connect-github/src/api/index.ts +4 -0
- package/connectors/connect-github/src/cli/index.ts +145 -0
- package/connectors/connect-gmail/src/api/history.ts +82 -0
- package/connectors/connect-gmail/src/api/index.ts +12 -0
- package/connectors/connect-gmail/src/api/settings.ts +280 -0
- package/connectors/connect-gmail/src/api/watch.ts +40 -0
- package/connectors/connect-gmail/src/cli/index.ts +237 -0
- package/connectors/connect-googleads/src/api/bulk.ts +354 -0
- package/connectors/connect-googleads/src/api/index.ts +4 -0
- package/connectors/connect-googleads/src/cli/index.ts +192 -0
- package/connectors/connect-googlecalendar/src/api/acl.ts +66 -0
- package/connectors/connect-googlecalendar/src/api/bulk.ts +452 -0
- package/connectors/connect-googlecalendar/src/api/freebusy.ts +35 -0
- package/connectors/connect-googlecalendar/src/api/index.ts +9 -0
- package/connectors/connect-googlecalendar/src/cli/index.ts +522 -0
- package/connectors/connect-googlecalendar/src/types/index.ts +90 -0
- package/connectors/connect-googlecontacts/src/api/bulk.ts +365 -0
- package/connectors/connect-googlecontacts/src/api/groups.ts +164 -0
- package/connectors/connect-googlecontacts/src/api/index.ts +8 -0
- package/connectors/connect-googlecontacts/src/cli/index.ts +340 -0
- package/connectors/connect-googledocs/src/api/bulk.ts +301 -0
- package/connectors/connect-googledocs/src/api/index.ts +4 -0
- package/connectors/connect-googledocs/src/cli/index.ts +187 -0
- package/connectors/connect-googledrive/src/api/bulk.ts +505 -0
- package/connectors/connect-googledrive/src/api/changes.ts +139 -0
- package/connectors/connect-googledrive/src/api/client.ts +34 -0
- package/connectors/connect-googledrive/src/api/index.ts +12 -0
- package/connectors/connect-googledrive/src/api/revisions.ts +132 -0
- package/connectors/connect-googledrive/src/cli/index.ts +624 -0
- package/connectors/connect-googledrive/src/index.ts +2 -0
- package/connectors/connect-googlephotos/src/api/bulk.ts +455 -0
- package/connectors/connect-googlephotos/src/api/index.ts +8 -0
- package/connectors/connect-googlephotos/src/cli/index.ts +185 -0
- package/connectors/connect-googletasks/src/api/bulk.ts +359 -0
- package/connectors/connect-googletasks/src/api/index.ts +1 -0
- package/connectors/connect-googletasks/src/cli/index.ts +178 -0
- package/connectors/connect-linear/src/api/bulk.ts +219 -0
- package/connectors/connect-linear/src/api/index.ts +4 -0
- package/connectors/connect-linear/src/cli/index.ts +116 -0
- package/connectors/connect-shopify/src/api/bulk.ts +288 -0
- package/connectors/connect-shopify/src/api/index.ts +4 -0
- package/connectors/connect-shopify/src/cli/index.ts +108 -0
- package/connectors/connect-slack/src/api/bulk.ts +237 -0
- package/connectors/connect-slack/src/api/index.ts +4 -0
- package/connectors/connect-slack/src/cli/index.ts +157 -0
- package/connectors/connect-stripe/src/api/bulk.ts +488 -0
- package/connectors/connect-stripe/src/api/index.ts +4 -0
- package/connectors/connect-stripe/src/cli/index.ts +206 -0
- package/dist/index.js +75 -33
- package/dist/lib/runner.d.ts +15 -5
- package/package.json +2 -2
- package/connectors/connect-cloudflare/bun.lock +0 -32
- package/connectors/connect-gmail/bin/index.js +0 -7027
- package/connectors/connect-gmail/dist/cli/index.js +0 -7035
- package/connectors/connect-gmail/dist/index.js +0 -2174
- package/dist/server/server.test.d.ts +0 -1
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
import type { GoogleCalendarClient } from './client.ts';
|
|
2
|
+
import type { Event, EventListResponse, ListEventsParams, EventInput } from '../types/index.ts';
|
|
3
|
+
|
|
4
|
+
// ============================================
|
|
5
|
+
// Bulk Operation Types
|
|
6
|
+
// ============================================
|
|
7
|
+
|
|
8
|
+
export interface BulkOperationOptions {
|
|
9
|
+
/** Calendar ID (default: 'primary') */
|
|
10
|
+
calendarId?: string;
|
|
11
|
+
/** Search query for events */
|
|
12
|
+
query?: string;
|
|
13
|
+
/** Filter by date range start (RFC3339) */
|
|
14
|
+
timeMin?: string;
|
|
15
|
+
/** Filter by date range end (RFC3339) */
|
|
16
|
+
timeMax?: string;
|
|
17
|
+
/** Maximum events to process (default: 100) */
|
|
18
|
+
maxResults?: number;
|
|
19
|
+
/** Maximum concurrent API calls (default: 10) */
|
|
20
|
+
concurrency?: number;
|
|
21
|
+
/** Dry run - don't actually modify, just preview */
|
|
22
|
+
dryRun?: boolean;
|
|
23
|
+
/** Progress callback */
|
|
24
|
+
onProgress?: (current: number, total: number, event: EventSummary) => void;
|
|
25
|
+
/** Error callback */
|
|
26
|
+
onError?: (error: Error, event: EventSummary) => void;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface BulkCreateOptions {
|
|
30
|
+
calendarId?: string;
|
|
31
|
+
/** Events to create in batch */
|
|
32
|
+
events: EventInput[];
|
|
33
|
+
/** Maximum concurrent API calls (default: 10) */
|
|
34
|
+
concurrency?: number;
|
|
35
|
+
/** Dry run - don't actually create, just preview */
|
|
36
|
+
dryRun?: boolean;
|
|
37
|
+
/** Send notifications to attendees */
|
|
38
|
+
sendNotifications?: boolean;
|
|
39
|
+
/** Progress callback */
|
|
40
|
+
onProgress?: (current: number, total: number, event: EventInput) => void;
|
|
41
|
+
/** Error callback */
|
|
42
|
+
onError?: (error: Error, event: EventInput) => void;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface BulkUpdateOptions extends BulkOperationOptions {
|
|
46
|
+
/** Partial event data to apply to all matching events */
|
|
47
|
+
updates: Partial<EventInput>;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface BulkRespondOptions extends BulkOperationOptions {
|
|
51
|
+
/** Response status to apply */
|
|
52
|
+
response: 'accepted' | 'declined' | 'tentative';
|
|
53
|
+
/** Optional comment to add */
|
|
54
|
+
comment?: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface EventSummary {
|
|
58
|
+
id: string;
|
|
59
|
+
summary: string;
|
|
60
|
+
status: string;
|
|
61
|
+
start: string;
|
|
62
|
+
end: string;
|
|
63
|
+
location?: string;
|
|
64
|
+
htmlLink?: string;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface BulkOperationResult {
|
|
68
|
+
total: number;
|
|
69
|
+
success: number;
|
|
70
|
+
failed: number;
|
|
71
|
+
skipped: number;
|
|
72
|
+
errors: Array<{ eventId: string; error: string }>;
|
|
73
|
+
processedEvents: EventSummary[];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface PreviewResult {
|
|
77
|
+
events: EventSummary[];
|
|
78
|
+
total: number;
|
|
79
|
+
query?: string;
|
|
80
|
+
calendarId: string;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ============================================
|
|
84
|
+
// Bulk Operations API
|
|
85
|
+
// ============================================
|
|
86
|
+
|
|
87
|
+
export class BulkApi {
|
|
88
|
+
private readonly client: GoogleCalendarClient;
|
|
89
|
+
|
|
90
|
+
constructor(client: GoogleCalendarClient) {
|
|
91
|
+
this.client = client;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ============================================
|
|
95
|
+
// Preview Operations
|
|
96
|
+
// ============================================
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Preview events matching a query without making changes
|
|
100
|
+
*/
|
|
101
|
+
async preview(calendarId: string = 'primary', options?: {
|
|
102
|
+
query?: string;
|
|
103
|
+
timeMin?: string;
|
|
104
|
+
timeMax?: string;
|
|
105
|
+
maxResults?: number;
|
|
106
|
+
}): Promise<PreviewResult> {
|
|
107
|
+
const events = await this.fetchEvents(calendarId, {
|
|
108
|
+
q: options?.query,
|
|
109
|
+
timeMin: options?.timeMin,
|
|
110
|
+
timeMax: options?.timeMax,
|
|
111
|
+
maxResults: options?.maxResults || 50,
|
|
112
|
+
orderBy: 'startTime',
|
|
113
|
+
singleEvents: true,
|
|
114
|
+
});
|
|
115
|
+
return { events, total: events.length, query: options?.query, calendarId };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ============================================
|
|
119
|
+
// Delete Operations
|
|
120
|
+
// ============================================
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Bulk delete events matching a query
|
|
124
|
+
*/
|
|
125
|
+
async delete(options: BulkOperationOptions): Promise<BulkOperationResult> {
|
|
126
|
+
const calendarId = options.calendarId || 'primary';
|
|
127
|
+
const events = await this.fetchEvents(calendarId, {
|
|
128
|
+
q: options.query,
|
|
129
|
+
timeMin: options.timeMin,
|
|
130
|
+
timeMax: options.timeMax,
|
|
131
|
+
maxResults: options.maxResults || 100,
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
return this.executeBatch(events, {
|
|
135
|
+
dryRun: options.dryRun || false,
|
|
136
|
+
concurrency: options.concurrency || 10,
|
|
137
|
+
onProgress: options.onProgress,
|
|
138
|
+
onError: options.onError,
|
|
139
|
+
operation: async (event) => {
|
|
140
|
+
await this.client.delete(
|
|
141
|
+
'/calendars/' + encodeURIComponent(calendarId) + '/events/' + encodeURIComponent(event.id)
|
|
142
|
+
);
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ============================================
|
|
148
|
+
// Update Operations
|
|
149
|
+
// ============================================
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Bulk update events matching a query with partial data
|
|
153
|
+
*/
|
|
154
|
+
async update(options: BulkUpdateOptions): Promise<BulkOperationResult> {
|
|
155
|
+
const calendarId = options.calendarId || 'primary';
|
|
156
|
+
const events = await this.fetchEvents(calendarId, {
|
|
157
|
+
q: options.query,
|
|
158
|
+
timeMin: options.timeMin,
|
|
159
|
+
timeMax: options.timeMax,
|
|
160
|
+
maxResults: options.maxResults || 100,
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
return this.executeBatch(events, {
|
|
164
|
+
dryRun: options.dryRun || false,
|
|
165
|
+
concurrency: options.concurrency || 10,
|
|
166
|
+
onProgress: options.onProgress,
|
|
167
|
+
onError: options.onError,
|
|
168
|
+
operation: async (event) => {
|
|
169
|
+
await this.client.patch<Event>(
|
|
170
|
+
'/calendars/' + encodeURIComponent(calendarId) + '/events/' + encodeURIComponent(event.id),
|
|
171
|
+
options.updates as Record<string, unknown>
|
|
172
|
+
);
|
|
173
|
+
},
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// ============================================
|
|
178
|
+
// Create Operations
|
|
179
|
+
// ============================================
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Bulk create events from a list
|
|
183
|
+
*/
|
|
184
|
+
async create(options: BulkCreateOptions): Promise<BulkOperationResult> {
|
|
185
|
+
const calendarId = options.calendarId || 'primary';
|
|
186
|
+
const result: BulkOperationResult = {
|
|
187
|
+
total: options.events.length,
|
|
188
|
+
success: 0,
|
|
189
|
+
failed: 0,
|
|
190
|
+
skipped: 0,
|
|
191
|
+
errors: [],
|
|
192
|
+
processedEvents: [],
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
if (options.events.length === 0) {
|
|
196
|
+
return result;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const chunks = this.chunkArray(options.events, options.concurrency || 10);
|
|
200
|
+
|
|
201
|
+
for (const chunk of chunks) {
|
|
202
|
+
await Promise.all(
|
|
203
|
+
chunk.map(async (eventInput) => {
|
|
204
|
+
try {
|
|
205
|
+
if (options.dryRun) {
|
|
206
|
+
result.success++;
|
|
207
|
+
result.processedEvents.push({
|
|
208
|
+
id: '(dry-run)',
|
|
209
|
+
summary: eventInput.summary || '(untitled)',
|
|
210
|
+
status: 'confirmed',
|
|
211
|
+
start: eventInput.start.dateTime || eventInput.start.date || '',
|
|
212
|
+
end: eventInput.end.dateTime || eventInput.end.date || '',
|
|
213
|
+
location: eventInput.location,
|
|
214
|
+
});
|
|
215
|
+
} else {
|
|
216
|
+
const created = await this.client.post<Event>(
|
|
217
|
+
'/calendars/' + encodeURIComponent(calendarId) + '/events',
|
|
218
|
+
eventInput as Record<string, unknown>,
|
|
219
|
+
{ sendNotifications: options.sendNotifications }
|
|
220
|
+
);
|
|
221
|
+
result.success++;
|
|
222
|
+
result.processedEvents.push({
|
|
223
|
+
id: created.id,
|
|
224
|
+
summary: created.summary || '(untitled)',
|
|
225
|
+
status: created.status || 'confirmed',
|
|
226
|
+
start: created.start?.dateTime || created.start?.date || '',
|
|
227
|
+
end: created.end?.dateTime || created.end?.date || '',
|
|
228
|
+
location: created.location,
|
|
229
|
+
htmlLink: created.htmlLink,
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (options.onProgress) {
|
|
234
|
+
options.onProgress(result.success + result.failed, result.total, eventInput);
|
|
235
|
+
}
|
|
236
|
+
} catch (err) {
|
|
237
|
+
result.failed++;
|
|
238
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
239
|
+
result.errors.push({ eventId: eventInput.summary || 'unknown', error: errorMessage });
|
|
240
|
+
|
|
241
|
+
if (options.onError) {
|
|
242
|
+
onError(err instanceof Error ? err : new Error(errorMessage), eventInput);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
})
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return result;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// ============================================
|
|
253
|
+
// Response Operations
|
|
254
|
+
// ============================================
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Bulk accept event invitations
|
|
258
|
+
*/
|
|
259
|
+
async accept(options: BulkRespondOptions): Promise<BulkOperationResult> {
|
|
260
|
+
return this.#respond({ ...options, response: 'accepted' });
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Bulk decline event invitations
|
|
265
|
+
*/
|
|
266
|
+
async decline(options: BulkRespondOptions): Promise<BulkOperationResult> {
|
|
267
|
+
return this.#respond({ ...options, response: 'declined' });
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Bulk mark events as tentative
|
|
272
|
+
*/
|
|
273
|
+
async tentative(options: BulkRespondOptions): Promise<BulkOperationResult> {
|
|
274
|
+
return this.#respond({ ...options, response: 'tentative' });
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// ============================================
|
|
278
|
+
// Helper Methods
|
|
279
|
+
// ============================================
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Bulk respond to events (accept/decline/tentative)
|
|
283
|
+
*/
|
|
284
|
+
async #respond(options: BulkRespondOptions): Promise<BulkOperationResult> {
|
|
285
|
+
const calendarId = options.calendarId || 'primary';
|
|
286
|
+
const events = await this.fetchEvents(calendarId, {
|
|
287
|
+
q: options.query,
|
|
288
|
+
timeMin: options.timeMin,
|
|
289
|
+
timeMax: options.timeMax,
|
|
290
|
+
maxResults: options.maxResults || 100,
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
return this.executeBatch(events, {
|
|
294
|
+
dryRun: options.dryRun || false,
|
|
295
|
+
concurrency: options.concurrency || 10,
|
|
296
|
+
onProgress: options.onProgress,
|
|
297
|
+
onError: options.onError,
|
|
298
|
+
operation: async (event) => {
|
|
299
|
+
// Get the current event to find self attendee
|
|
300
|
+
const current = await this.client.get<Event>(
|
|
301
|
+
'/calendars/' + encodeURIComponent(calendarId) + '/events/' + encodeURIComponent(event.id)
|
|
302
|
+
);
|
|
303
|
+
|
|
304
|
+
if (current.attendees) {
|
|
305
|
+
for (const attendee of current.attendees) {
|
|
306
|
+
if (attendee.self) {
|
|
307
|
+
attendee.responseStatus = options.response;
|
|
308
|
+
if (options.comment) {
|
|
309
|
+
attendee.comment = options.comment;
|
|
310
|
+
}
|
|
311
|
+
break;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
await this.client.patch<Event>(
|
|
317
|
+
'/calendars/' + encodeURIComponent(calendarId) + '/events/' + encodeURIComponent(event.id),
|
|
318
|
+
{ attendees: current.attendees } as Record<string, unknown>,
|
|
319
|
+
{ sendUpdates: 'none' }
|
|
320
|
+
);
|
|
321
|
+
},
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Fetch events matching criteria
|
|
327
|
+
*/
|
|
328
|
+
async fetchEvents(calendarId: string, params: {
|
|
329
|
+
q?: string;
|
|
330
|
+
timeMin?: string;
|
|
331
|
+
timeMax?: string;
|
|
332
|
+
maxResults?: number;
|
|
333
|
+
orderBy?: string;
|
|
334
|
+
singleEvents?: boolean;
|
|
335
|
+
}): Promise<EventSummary[]> {
|
|
336
|
+
const events: EventSummary[] = [];
|
|
337
|
+
let pageToken: string | undefined;
|
|
338
|
+
const max = params.maxResults || 100;
|
|
339
|
+
|
|
340
|
+
while (events.length < max) {
|
|
341
|
+
const requestParams: Record<string, string | number | boolean | undefined> = {
|
|
342
|
+
maxResults: Math.min(100, max - events.length),
|
|
343
|
+
pageToken,
|
|
344
|
+
orderBy: params.orderBy,
|
|
345
|
+
singleEvents: params.singleEvents,
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
if (params.q) requestParams.q = params.q;
|
|
349
|
+
if (params.timeMin) requestParams.timeMin = params.timeMin;
|
|
350
|
+
if (params.timeMax) requestParams.timeMax = params.timeMax;
|
|
351
|
+
|
|
352
|
+
const response = await this.client.get<EventListResponse>(
|
|
353
|
+
'/calendars/' + encodeURIComponent(calendarId) + '/events',
|
|
354
|
+
requestParams
|
|
355
|
+
);
|
|
356
|
+
|
|
357
|
+
if (!response.items || response.items.length === 0) {
|
|
358
|
+
break;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
for (const e of response.items) {
|
|
362
|
+
events.push({
|
|
363
|
+
id: e.id,
|
|
364
|
+
summary: e.summary || '(untitled)',
|
|
365
|
+
status: e.status || 'confirmed',
|
|
366
|
+
start: e.start?.dateTime || e.start?.date || '',
|
|
367
|
+
end: e.end?.dateTime || e.end?.date || '',
|
|
368
|
+
location: e.location,
|
|
369
|
+
htmlLink: e.htmlLink,
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
pageToken = response.nextPageToken;
|
|
374
|
+
if (!pageToken) break;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
return events;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Execute operations in batches with concurrency control
|
|
382
|
+
*/
|
|
383
|
+
private async executeBatch(
|
|
384
|
+
events: EventSummary[],
|
|
385
|
+
options: {
|
|
386
|
+
dryRun: boolean;
|
|
387
|
+
concurrency: number;
|
|
388
|
+
onProgress?: (current: number, total: number, event: EventSummary) => void;
|
|
389
|
+
onError?: (error: Error, event: EventSummary) => void;
|
|
390
|
+
operation: (event: EventSummary) => Promise<void>;
|
|
391
|
+
}
|
|
392
|
+
): Promise<BulkOperationResult> {
|
|
393
|
+
const { dryRun, concurrency, onProgress, onError, operation } = options;
|
|
394
|
+
|
|
395
|
+
const result: BulkOperationResult = {
|
|
396
|
+
total: events.length,
|
|
397
|
+
success: 0,
|
|
398
|
+
failed: 0,
|
|
399
|
+
skipped: 0,
|
|
400
|
+
errors: [],
|
|
401
|
+
processedEvents: [],
|
|
402
|
+
};
|
|
403
|
+
|
|
404
|
+
if (events.length === 0) {
|
|
405
|
+
return result;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
const chunks = this.chunkArray(events, concurrency);
|
|
409
|
+
|
|
410
|
+
for (const chunk of chunks) {
|
|
411
|
+
await Promise.all(
|
|
412
|
+
chunk.map(async (event) => {
|
|
413
|
+
try {
|
|
414
|
+
if (dryRun) {
|
|
415
|
+
result.success++;
|
|
416
|
+
result.processedEvents.push(event);
|
|
417
|
+
} else {
|
|
418
|
+
await operation(event);
|
|
419
|
+
result.success++;
|
|
420
|
+
result.processedEvents.push(event);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
if (onProgress) {
|
|
424
|
+
onProgress(result.success + result.failed, result.total, event);
|
|
425
|
+
}
|
|
426
|
+
} catch (err) {
|
|
427
|
+
result.failed++;
|
|
428
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
429
|
+
result.errors.push({ eventId: event.id, error: errorMessage });
|
|
430
|
+
|
|
431
|
+
if (onError) {
|
|
432
|
+
onError(err instanceof Error ? err : new Error(errorMessage), event);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
})
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
return result;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Split array into chunks
|
|
444
|
+
*/
|
|
445
|
+
private chunkArray<T>(array: T[], size: number): T[][] {
|
|
446
|
+
const chunks: T[][] = [];
|
|
447
|
+
for (let i = 0; i < array.length; i += size) {
|
|
448
|
+
chunks.push(array.slice(i, i + size));
|
|
449
|
+
}
|
|
450
|
+
return chunks;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { GoogleCalendarClient } from './client';
|
|
2
|
+
import type { FreeBusyRequest, FreeBusyCalendarResponse } from '../types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Free/Busy API module - query free/busy time for calendars
|
|
6
|
+
*/
|
|
7
|
+
export class FreeBusyApi {
|
|
8
|
+
constructor(private readonly client: GoogleCalendarClient) {}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Query free/busy information for one or more calendars
|
|
12
|
+
*/
|
|
13
|
+
async query(request: FreeBusyRequest): Promise<FreeBusyCalendarResponse> {
|
|
14
|
+
return this.client.post<FreeBusyCalendarResponse>('/freeBusy', {
|
|
15
|
+
timeMin: request.timeMin || new Date().toISOString(),
|
|
16
|
+
timeMax: request.timeMax || new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(),
|
|
17
|
+
items: request.items,
|
|
18
|
+
groupExpansionMax: request.groupExpansionMax,
|
|
19
|
+
timeZone: request.timeZone,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Quick check if a calendar is free during a time range
|
|
25
|
+
*/
|
|
26
|
+
async isFree(calendarId: string, start: string, end: string): Promise<boolean> {
|
|
27
|
+
const result = await this.query({
|
|
28
|
+
timeMin: start,
|
|
29
|
+
timeMax: end,
|
|
30
|
+
items: [{ id: calendarId }],
|
|
31
|
+
});
|
|
32
|
+
const calendar = result.calendars[calendarId];
|
|
33
|
+
return !calendar || calendar.busy.length === 0;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -2,6 +2,9 @@ import type { GoogleCalendarConfig } from '../types';
|
|
|
2
2
|
import { GoogleCalendarClient, CALENDAR_SCOPES } from './client';
|
|
3
3
|
import { CalendarsApi } from './calendars';
|
|
4
4
|
import { EventsApi } from './events';
|
|
5
|
+
import { BulkApi } from './bulk';
|
|
6
|
+
import { FreeBusyApi } from './freebusy';
|
|
7
|
+
import { AclApi } from './acl';
|
|
5
8
|
|
|
6
9
|
/**
|
|
7
10
|
* Main GoogleCalendar class
|
|
@@ -12,11 +15,17 @@ export class GoogleCalendar {
|
|
|
12
15
|
// API modules
|
|
13
16
|
public readonly calendars: CalendarsApi;
|
|
14
17
|
public readonly events: EventsApi;
|
|
18
|
+
public readonly bulk: BulkApi;
|
|
19
|
+
public readonly freebusy: FreeBusyApi;
|
|
20
|
+
public readonly acl: AclApi;
|
|
15
21
|
|
|
16
22
|
constructor(config: GoogleCalendarConfig) {
|
|
17
23
|
this.client = new GoogleCalendarClient(config);
|
|
18
24
|
this.calendars = new CalendarsApi(this.client);
|
|
19
25
|
this.events = new EventsApi(this.client);
|
|
26
|
+
this.bulk = new BulkApi(this.client);
|
|
27
|
+
this.freebusy = new FreeBusyApi(this.client);
|
|
28
|
+
this.acl = new AclApi(this.client);
|
|
20
29
|
}
|
|
21
30
|
|
|
22
31
|
/**
|