@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,365 @@
|
|
|
1
|
+
import type { GoogleContactsClient } from './client';
|
|
2
|
+
import type { Contact, ContactsListResponse } from '../types';
|
|
3
|
+
|
|
4
|
+
// ============================================
|
|
5
|
+
// Bulk Operation Types
|
|
6
|
+
// ============================================
|
|
7
|
+
|
|
8
|
+
export interface BulkOperationOptions {
|
|
9
|
+
/** Search query to match contacts (matches name, email, etc.) */
|
|
10
|
+
query?: string;
|
|
11
|
+
/** Maximum contacts to process (default: 100) */
|
|
12
|
+
maxResults?: number;
|
|
13
|
+
/** Maximum concurrent API calls (default: 10) */
|
|
14
|
+
concurrency?: number;
|
|
15
|
+
/** Dry run - don't actually modify */
|
|
16
|
+
dryRun?: boolean;
|
|
17
|
+
/** Progress callback */
|
|
18
|
+
onProgress?: (current: number, total: number, contact: ContactSummary) => void;
|
|
19
|
+
/** Error callback */
|
|
20
|
+
onError?: (error: Error, contact: ContactSummary) => void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface BulkDeleteOptions extends BulkOperationOptions {}
|
|
24
|
+
|
|
25
|
+
export interface BulkUpdateOptions extends BulkOperationOptions {
|
|
26
|
+
/** Fields to update on all matching contacts */
|
|
27
|
+
updates: {
|
|
28
|
+
givenName?: string;
|
|
29
|
+
familyName?: string;
|
|
30
|
+
organization?: { name?: string; title?: string };
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface BulkGroupOptions {
|
|
35
|
+
/** Contact resource names to add */
|
|
36
|
+
contactNames?: string[];
|
|
37
|
+
/** Or use a query to select contacts */
|
|
38
|
+
query?: string;
|
|
39
|
+
maxResults?: number;
|
|
40
|
+
concurrency?: number;
|
|
41
|
+
dryRun?: boolean;
|
|
42
|
+
onProgress?: (current: number, total: number, contact: ContactSummary) => void;
|
|
43
|
+
onError?: (error: Error, contact: ContactSummary) => void;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface ContactSummary {
|
|
47
|
+
resourceName: string;
|
|
48
|
+
displayName: string;
|
|
49
|
+
emails: string[];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface BulkOperationResult {
|
|
53
|
+
total: number;
|
|
54
|
+
success: number;
|
|
55
|
+
failed: number;
|
|
56
|
+
skipped: number;
|
|
57
|
+
errors: Array<{ contactName: string; error: string }>;
|
|
58
|
+
processedContacts: ContactSummary[];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface PreviewResult {
|
|
62
|
+
contacts: ContactSummary[];
|
|
63
|
+
total: number;
|
|
64
|
+
query?: string;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ============================================
|
|
68
|
+
// Bulk Operations API
|
|
69
|
+
// ============================================
|
|
70
|
+
|
|
71
|
+
export class BulkApi {
|
|
72
|
+
private readonly client: GoogleContactsClient;
|
|
73
|
+
|
|
74
|
+
constructor(client: GoogleContactsClient) {
|
|
75
|
+
this.client = client;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ============================================
|
|
79
|
+
// Preview
|
|
80
|
+
// ============================================
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Preview contacts matching a query
|
|
84
|
+
*/
|
|
85
|
+
async preview(options?: { query?: string; maxResults?: number }): Promise<PreviewResult> {
|
|
86
|
+
const contacts = await this.fetchContacts({
|
|
87
|
+
query: options?.query,
|
|
88
|
+
maxResults: options?.maxResults || 50,
|
|
89
|
+
});
|
|
90
|
+
return { contacts, total: contacts.length, query: options?.query };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ============================================
|
|
94
|
+
// Delete
|
|
95
|
+
// ============================================
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Bulk delete contacts matching a query
|
|
99
|
+
*/
|
|
100
|
+
async delete(options: BulkDeleteOptions): Promise<BulkOperationResult> {
|
|
101
|
+
const contacts = await this.fetchContacts({
|
|
102
|
+
query: options.query,
|
|
103
|
+
maxResults: options.maxResults || 100,
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
return this.executeBatch(contacts, {
|
|
107
|
+
dryRun: options.dryRun || false,
|
|
108
|
+
concurrency: options.concurrency || 10,
|
|
109
|
+
onProgress: options.onProgress,
|
|
110
|
+
onError: options.onError,
|
|
111
|
+
operation: async (contact) => {
|
|
112
|
+
await this.client.delete(`/v1/${contact.resourceName}:deleteContact`);
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ============================================
|
|
118
|
+
// Update
|
|
119
|
+
// ============================================
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Bulk update contacts with partial data
|
|
123
|
+
*/
|
|
124
|
+
async update(options: BulkUpdateOptions): Promise<BulkOperationResult> {
|
|
125
|
+
const contacts = await this.fetchContacts({
|
|
126
|
+
query: options.query,
|
|
127
|
+
maxResults: options.maxResults || 100,
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
return this.executeBatch(contacts, {
|
|
131
|
+
dryRun: options.dryRun || false,
|
|
132
|
+
concurrency: options.concurrency || 10,
|
|
133
|
+
onProgress: options.onProgress,
|
|
134
|
+
onError: options.onError,
|
|
135
|
+
operation: async (contact) => {
|
|
136
|
+
const updateFields: string[] = [];
|
|
137
|
+
const body: Record<string, unknown> = {};
|
|
138
|
+
|
|
139
|
+
if (options.updates.givenName || options.updates.familyName) {
|
|
140
|
+
body.names = [{
|
|
141
|
+
givenName: options.updates.givenName,
|
|
142
|
+
familyName: options.updates.familyName,
|
|
143
|
+
}];
|
|
144
|
+
updateFields.push('names');
|
|
145
|
+
}
|
|
146
|
+
if (options.updates.organization) {
|
|
147
|
+
body.organizations = [{
|
|
148
|
+
name: options.updates.organization.name,
|
|
149
|
+
title: options.updates.organization.title,
|
|
150
|
+
}];
|
|
151
|
+
updateFields.push('organizations');
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
await this.client.patch<Contact>(`/v1/${contact.resourceName}:updateContact`, body, {
|
|
155
|
+
updatePersonFields: updateFields.join(','),
|
|
156
|
+
});
|
|
157
|
+
},
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ============================================
|
|
162
|
+
// Add to Group
|
|
163
|
+
// ============================================
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Bulk add contacts to a contact group
|
|
167
|
+
*/
|
|
168
|
+
async addToGroup(groupResourceName: string, options: BulkGroupOptions): Promise<BulkOperationResult> {
|
|
169
|
+
let contactNames = options.contactNames;
|
|
170
|
+
|
|
171
|
+
// If no names provided, resolve from query
|
|
172
|
+
if (!contactNames || contactNames.length === 0) {
|
|
173
|
+
const contacts = await this.fetchContacts({
|
|
174
|
+
query: options.query,
|
|
175
|
+
maxResults: options.maxResults || 100,
|
|
176
|
+
});
|
|
177
|
+
contactNames = contacts.map(c => c.resourceName);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const summaries = contactNames.map(name => ({
|
|
181
|
+
resourceName: name,
|
|
182
|
+
displayName: name,
|
|
183
|
+
emails: [],
|
|
184
|
+
}));
|
|
185
|
+
|
|
186
|
+
return this.executeBatch(summaries, {
|
|
187
|
+
dryRun: options.dryRun || false,
|
|
188
|
+
concurrency: options.concurrency || 10,
|
|
189
|
+
onProgress: options.onProgress,
|
|
190
|
+
onError: options.onError,
|
|
191
|
+
operation: async (contact) => {
|
|
192
|
+
await this.client.post(`/v1/${groupResourceName}:modifyMembers`, {
|
|
193
|
+
membersToAdd: [{ contactGroupMembership: { contactGroupResourceName: groupResourceName } }],
|
|
194
|
+
resourceNames: [contact.resourceName],
|
|
195
|
+
});
|
|
196
|
+
},
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// ============================================
|
|
201
|
+
// Remove from Group
|
|
202
|
+
// ============================================
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Bulk remove contacts from a contact group
|
|
206
|
+
*/
|
|
207
|
+
async removeFromGroup(groupResourceName: string, options: BulkGroupOptions): Promise<BulkOperationResult> {
|
|
208
|
+
let contactNames = options.contactNames;
|
|
209
|
+
|
|
210
|
+
if (!contactNames || contactNames.length === 0) {
|
|
211
|
+
const contacts = await this.fetchContacts({
|
|
212
|
+
query: options.query,
|
|
213
|
+
maxResults: options.maxResults || 100,
|
|
214
|
+
});
|
|
215
|
+
contactNames = contacts.map(c => c.resourceName);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const summaries = contactNames.map(name => ({
|
|
219
|
+
resourceName: name,
|
|
220
|
+
displayName: name,
|
|
221
|
+
emails: [],
|
|
222
|
+
}));
|
|
223
|
+
|
|
224
|
+
return this.executeBatch(summaries, {
|
|
225
|
+
dryRun: options.dryRun || false,
|
|
226
|
+
concurrency: options.concurrency || 10,
|
|
227
|
+
onProgress: options.onProgress,
|
|
228
|
+
onError: options.onError,
|
|
229
|
+
operation: async (contact) => {
|
|
230
|
+
await this.client.post(`/v1/${groupResourceName}:modifyMembers`, {
|
|
231
|
+
membersToDelete: [{ contactId: contact.resourceName.split('/').pop() }],
|
|
232
|
+
});
|
|
233
|
+
},
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// ============================================
|
|
238
|
+
// Helper Methods
|
|
239
|
+
// ============================================
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Fetch contacts matching a query
|
|
243
|
+
*/
|
|
244
|
+
async fetchContacts(params: { query?: string; maxResults?: number }): Promise<ContactSummary[]> {
|
|
245
|
+
const contacts: ContactSummary[] = [];
|
|
246
|
+
let pageToken: string | undefined;
|
|
247
|
+
const max = params.maxResults || 100;
|
|
248
|
+
const personFields = 'names,emailAddresses,organizations,memberships';
|
|
249
|
+
|
|
250
|
+
while (contacts.length < max) {
|
|
251
|
+
let response: ContactsListResponse;
|
|
252
|
+
|
|
253
|
+
if (params.query) {
|
|
254
|
+
// Use search endpoint
|
|
255
|
+
const searchResult = await this.client.get<{
|
|
256
|
+
results?: Array<{ person: Contact }>;
|
|
257
|
+
nextPageToken?: string;
|
|
258
|
+
}>('/v1/people:searchContacts', {
|
|
259
|
+
query: params.query,
|
|
260
|
+
pageSize: Math.min(100, max - contacts.length),
|
|
261
|
+
pageToken,
|
|
262
|
+
readMask: personFields,
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
if (!searchResult.results || searchResult.results.length === 0) break;
|
|
266
|
+
|
|
267
|
+
for (const r of searchResult.results) {
|
|
268
|
+
const c = r.person;
|
|
269
|
+
contacts.push(this.toSummary(c));
|
|
270
|
+
}
|
|
271
|
+
pageToken = searchResult.nextPageToken;
|
|
272
|
+
} else {
|
|
273
|
+
// Use list endpoint
|
|
274
|
+
response = await this.client.get<ContactsListResponse>('/v1/people/me/connections', {
|
|
275
|
+
pageSize: Math.min(100, max - contacts.length),
|
|
276
|
+
pageToken,
|
|
277
|
+
personFields,
|
|
278
|
+
sortOrder: 'LAST_MODIFIED_DESCENDING',
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
if (!response.connections || response.connections.length === 0) break;
|
|
282
|
+
|
|
283
|
+
for (const c of response.connections) {
|
|
284
|
+
contacts.push(this.toSummary(c));
|
|
285
|
+
}
|
|
286
|
+
pageToken = response.nextPageToken;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (!pageToken) break;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return contacts;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
private toSummary(contact: Contact): ContactSummary {
|
|
296
|
+
return {
|
|
297
|
+
resourceName: contact.resourceName,
|
|
298
|
+
displayName: contact.names?.[0]?.displayName || '(unnamed)',
|
|
299
|
+
emails: (contact.emailAddresses || []).map(e => e.value).filter(Boolean),
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Execute operations in batches with concurrency control
|
|
305
|
+
*/
|
|
306
|
+
private async executeBatch(
|
|
307
|
+
contacts: ContactSummary[],
|
|
308
|
+
options: {
|
|
309
|
+
dryRun: boolean;
|
|
310
|
+
concurrency: number;
|
|
311
|
+
onProgress?: (current: number, total: number, contact: ContactSummary) => void;
|
|
312
|
+
onError?: (error: Error, contact: ContactSummary) => void;
|
|
313
|
+
operation: (contact: ContactSummary) => Promise<void>;
|
|
314
|
+
}
|
|
315
|
+
): Promise<BulkOperationResult> {
|
|
316
|
+
const { dryRun, concurrency, onProgress, onError, operation } = options;
|
|
317
|
+
|
|
318
|
+
const result: BulkOperationResult = {
|
|
319
|
+
total: contacts.length,
|
|
320
|
+
success: 0,
|
|
321
|
+
failed: 0,
|
|
322
|
+
skipped: 0,
|
|
323
|
+
errors: [],
|
|
324
|
+
processedContacts: [],
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
if (contacts.length === 0) return result;
|
|
328
|
+
|
|
329
|
+
const chunks = this.chunkArray(contacts, concurrency);
|
|
330
|
+
|
|
331
|
+
for (const chunk of chunks) {
|
|
332
|
+
await Promise.all(
|
|
333
|
+
chunk.map(async (contact) => {
|
|
334
|
+
try {
|
|
335
|
+
if (dryRun) {
|
|
336
|
+
result.success++;
|
|
337
|
+
result.processedContacts.push(contact);
|
|
338
|
+
} else {
|
|
339
|
+
await operation(contact);
|
|
340
|
+
result.success++;
|
|
341
|
+
result.processedContacts.push(contact);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
if (onProgress) onProgress(result.success + result.failed, result.total, contact);
|
|
345
|
+
} catch (err) {
|
|
346
|
+
result.failed++;
|
|
347
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
348
|
+
result.errors.push({ contactName: contact.resourceName, error: errorMessage });
|
|
349
|
+
if (onError) onError(err instanceof Error ? err : new Error(errorMessage), contact);
|
|
350
|
+
}
|
|
351
|
+
})
|
|
352
|
+
);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
return result;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
private chunkArray<T>(array: T[], size: number): T[][] {
|
|
359
|
+
const chunks: T[][] = [];
|
|
360
|
+
for (let i = 0; i < array.length; i += size) {
|
|
361
|
+
chunks.push(array.slice(i, i + size));
|
|
362
|
+
}
|
|
363
|
+
return chunks;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import type { GoogleContactsClient } from './client';
|
|
2
|
+
import type { ContactGroup, ContactGroupsListResponse } from '../types';
|
|
3
|
+
|
|
4
|
+
// ============================================
|
|
5
|
+
// Group Request/Response Types
|
|
6
|
+
// ============================================
|
|
7
|
+
|
|
8
|
+
export interface CreateGroupParams {
|
|
9
|
+
name: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface UpdateGroupParams {
|
|
13
|
+
name?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface GetGroupMembersOptions {
|
|
17
|
+
pageSize?: number;
|
|
18
|
+
pageToken?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface GetGroupMembersResponse {
|
|
22
|
+
connections?: Array<{
|
|
23
|
+
resourceName: string;
|
|
24
|
+
etag?: string;
|
|
25
|
+
names?: Array<{
|
|
26
|
+
displayName?: string;
|
|
27
|
+
givenName?: string;
|
|
28
|
+
familyName?: string;
|
|
29
|
+
}>;
|
|
30
|
+
emailAddresses?: Array<{ value: string }>;
|
|
31
|
+
phoneNumbers?: Array<{ value: string }>;
|
|
32
|
+
}>;
|
|
33
|
+
nextPageToken?: string;
|
|
34
|
+
totalItems?: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface ModifyMembershipParams {
|
|
38
|
+
contactResourceName: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Groups API module - CRUD and membership management for Google contact groups
|
|
43
|
+
*/
|
|
44
|
+
export class GroupsApi {
|
|
45
|
+
constructor(private readonly client: GoogleContactsClient) {}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* List contact groups
|
|
49
|
+
*/
|
|
50
|
+
async list(options: { pageSize?: number; pageToken?: string; groupFields?: string } = {}): Promise<ContactGroupsListResponse> {
|
|
51
|
+
const { pageSize = 100, pageToken, groupFields } = options;
|
|
52
|
+
const params: Record<string, string | number> = { pageSize };
|
|
53
|
+
if (pageToken) params.pageToken = pageToken;
|
|
54
|
+
if (groupFields) params.groupFields = groupFields;
|
|
55
|
+
return this.client.get<ContactGroupsListResponse>('/v1/contactGroups', params);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Get a specific group by resource name
|
|
60
|
+
*/
|
|
61
|
+
async get(groupResourceName: string): Promise<ContactGroup> {
|
|
62
|
+
const name = groupResourceName.startsWith('contactGroups/') ? groupResourceName : `contactGroups/${groupResourceName}`;
|
|
63
|
+
return this.client.get<ContactGroup>(`/v1/${name}`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Create a new contact group
|
|
68
|
+
*/
|
|
69
|
+
async create(params: CreateGroupParams): Promise<ContactGroup> {
|
|
70
|
+
return this.client.post<ContactGroup>('/v1/contactGroups', {
|
|
71
|
+
contactGroup: {
|
|
72
|
+
name: params.name,
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Update a contact group's name
|
|
79
|
+
*/
|
|
80
|
+
async update(groupResourceName: string, params: UpdateGroupParams): Promise<ContactGroup> {
|
|
81
|
+
const name = groupResourceName.startsWith('contactGroups/') ? groupResourceName : `contactGroups/${groupResourceName}`;
|
|
82
|
+
|
|
83
|
+
const body: Record<string, unknown> = {};
|
|
84
|
+
if (params.name !== undefined) {
|
|
85
|
+
body.name = params.name;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return this.client.put<ContactGroup>(`/v1/${name}`, body, {
|
|
89
|
+
updateMask: 'name',
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Delete a contact group
|
|
95
|
+
*/
|
|
96
|
+
async delete(groupResourceName: string): Promise<void> {
|
|
97
|
+
const name = groupResourceName.startsWith('contactGroups/') ? groupResourceName : `contactGroups/${groupResourceName}`;
|
|
98
|
+
await this.client.delete(`/v1/${name}`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* List members of a contact group
|
|
103
|
+
*/
|
|
104
|
+
async getMembers(groupResourceName: string, options: GetGroupMembersOptions = {}): Promise<GetGroupMembersResponse> {
|
|
105
|
+
const name = groupResourceName.startsWith('contactGroups/') ? groupResourceName : `contactGroups/${groupResourceName}`;
|
|
106
|
+
const { pageSize = 100, pageToken } = options;
|
|
107
|
+
const params: Record<string, string | number> = { pageSize };
|
|
108
|
+
if (pageToken) params.pageToken = pageToken;
|
|
109
|
+
return this.client.get<GetGroupMembersResponse>(`/v1/${name}/members`, params);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Iterate over all members of a group (handles pagination)
|
|
114
|
+
*/
|
|
115
|
+
async *listAllMembers(groupResourceName: string, options: Omit<GetGroupMembersOptions, 'pageToken'> = {}): AsyncGenerator<{
|
|
116
|
+
resourceName: string;
|
|
117
|
+
displayName?: string;
|
|
118
|
+
emails?: string[];
|
|
119
|
+
phones?: string[];
|
|
120
|
+
}> {
|
|
121
|
+
let pageToken: string | undefined;
|
|
122
|
+
do {
|
|
123
|
+
const response = await this.getMembers(groupResourceName, { ...options, pageToken });
|
|
124
|
+
const connections = response.connections || [];
|
|
125
|
+
for (const member of connections) {
|
|
126
|
+
yield {
|
|
127
|
+
resourceName: member.resourceName,
|
|
128
|
+
displayName: member.names?.[0]?.displayName,
|
|
129
|
+
emails: member.emailAddresses?.map(e => e.value) || [],
|
|
130
|
+
phones: member.phoneNumbers?.map(p => p.value) || [],
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
pageToken = response.nextPageToken;
|
|
134
|
+
} while (pageToken);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Add a contact to a group
|
|
139
|
+
*/
|
|
140
|
+
async addMember(groupResourceName: string, contactResourceName: string): Promise<void> {
|
|
141
|
+
const group = groupResourceName.startsWith('contactGroups/') ? groupResourceName : `contactGroups/${groupResourceName}`;
|
|
142
|
+
const contact = contactResourceName.startsWith('people/') ? contactResourceName : `people/${contactResourceName}`;
|
|
143
|
+
await this.client.post(`/v1/${group}/members:batchCreate`, {
|
|
144
|
+
contactMemberships: [
|
|
145
|
+
{
|
|
146
|
+
contact: {
|
|
147
|
+
resourceName: contact,
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
],
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Remove a contact from a group
|
|
156
|
+
*/
|
|
157
|
+
async removeMember(groupResourceName: string, contactResourceName: string): Promise<void> {
|
|
158
|
+
const group = groupResourceName.startsWith('contactGroups/') ? groupResourceName : `contactGroups/${groupResourceName}`;
|
|
159
|
+
const contact = contactResourceName.startsWith('people/') ? contactResourceName : `people/${contactResourceName}`;
|
|
160
|
+
await this.client.post(`/v1/${group}/members:batchDelete`, {
|
|
161
|
+
contactResourceNames: [contact],
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { GoogleContactsConfig, OAuthTokens } from '../types';
|
|
2
2
|
import { GoogleContactsClient } from './client';
|
|
3
3
|
import { ContactsApi } from './contacts';
|
|
4
|
+
import { GroupsApi } from './groups';
|
|
5
|
+
import { BulkApi } from './bulk';
|
|
4
6
|
|
|
5
7
|
/**
|
|
6
8
|
* Main GoogleContacts class - entry point for the API
|
|
@@ -10,10 +12,14 @@ export class GoogleContacts {
|
|
|
10
12
|
|
|
11
13
|
// API modules
|
|
12
14
|
public readonly contacts: ContactsApi;
|
|
15
|
+
public readonly groups: GroupsApi;
|
|
16
|
+
public readonly bulk: BulkApi;
|
|
13
17
|
|
|
14
18
|
constructor(config: GoogleContactsConfig) {
|
|
15
19
|
this.client = new GoogleContactsClient(config);
|
|
16
20
|
this.contacts = new ContactsApi(this.client);
|
|
21
|
+
this.groups = new GroupsApi(this.client);
|
|
22
|
+
this.bulk = new BulkApi(this.client);
|
|
17
23
|
}
|
|
18
24
|
|
|
19
25
|
/**
|
|
@@ -97,3 +103,5 @@ export class GoogleContacts {
|
|
|
97
103
|
|
|
98
104
|
export { GoogleContactsClient } from './client';
|
|
99
105
|
export { ContactsApi } from './contacts';
|
|
106
|
+
export { GroupsApi } from './groups';
|
|
107
|
+
export { BulkApi } from './bulk';
|