@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,354 @@
|
|
|
1
|
+
import type { GoogleAdsClient } from './client';
|
|
2
|
+
import type { MutateResponse } from '../types';
|
|
3
|
+
|
|
4
|
+
// ============================================
|
|
5
|
+
// Bulk Operation Types
|
|
6
|
+
// ============================================
|
|
7
|
+
|
|
8
|
+
export interface BulkOperationOptions {
|
|
9
|
+
/** Maximum concurrent API calls (default: 10) */
|
|
10
|
+
concurrency?: number;
|
|
11
|
+
/** Dry run - don't actually modify */
|
|
12
|
+
dryRun?: boolean;
|
|
13
|
+
/** Progress callback */
|
|
14
|
+
onProgress?: (current: number, total: number, item: unknown) => void;
|
|
15
|
+
/** Error callback */
|
|
16
|
+
onError?: (error: Error, item: unknown) => void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// --- Status Change Bulk Operations ---
|
|
20
|
+
|
|
21
|
+
export interface StatusChangeEntry {
|
|
22
|
+
resourceId: string;
|
|
23
|
+
/** The resource path segment, e.g. 'campaigns', 'adGroups', 'adGroupAds', 'adGroupCriteria' */
|
|
24
|
+
resourceType: 'campaign' | 'adGroup' | 'ad' | 'keyword';
|
|
25
|
+
/** Additional identifiers for composite resources */
|
|
26
|
+
parentId?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface BulkStatusChangeOptions extends BulkOperationOptions {
|
|
30
|
+
/** Entries to update */
|
|
31
|
+
entries: StatusChangeEntry[];
|
|
32
|
+
/** Target status */
|
|
33
|
+
status: 'ENABLED' | 'PAUSED' | 'REMOVED';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface BulkStatusChangeResult {
|
|
37
|
+
total: number;
|
|
38
|
+
success: number;
|
|
39
|
+
failed: number;
|
|
40
|
+
errors: Array<{ resourceId: string; error: string }>;
|
|
41
|
+
results: Array<{ resourceId: string; response: MutateResponse }>;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// --- Campaign Bulk Operations ---
|
|
45
|
+
|
|
46
|
+
export interface BulkCampaignOptions extends BulkOperationOptions {
|
|
47
|
+
campaignIds: string[];
|
|
48
|
+
status: 'ENABLED' | 'PAUSED' | 'REMOVED';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface BulkCampaignResult {
|
|
52
|
+
total: number;
|
|
53
|
+
success: number;
|
|
54
|
+
failed: number;
|
|
55
|
+
errors: Array<{ campaignId: string; error: string }>;
|
|
56
|
+
results: Array<{ campaignId: string; response: MutateResponse }>;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// --- Ad Group Bulk Operations ---
|
|
60
|
+
|
|
61
|
+
export interface BulkAdGroupOptions extends BulkOperationOptions {
|
|
62
|
+
adGroupIds: string[];
|
|
63
|
+
status: 'ENABLED' | 'PAUSED';
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface BulkAdGroupResult {
|
|
67
|
+
total: number;
|
|
68
|
+
success: number;
|
|
69
|
+
failed: number;
|
|
70
|
+
errors: Array<{ adGroupId: string; error: string }>;
|
|
71
|
+
results: Array<{ adGroupId: string; response: MutateResponse }>;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// --- Ad Bulk Operations ---
|
|
75
|
+
|
|
76
|
+
export interface BulkAdOptions extends BulkOperationOptions {
|
|
77
|
+
/** Entries with adGroupId and adId pairs */
|
|
78
|
+
entries: Array<{ adGroupId: string; adId: string }>;
|
|
79
|
+
status: 'ENABLED' | 'PAUSED' | 'REMOVED';
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface BulkAdResult {
|
|
83
|
+
total: number;
|
|
84
|
+
success: number;
|
|
85
|
+
failed: number;
|
|
86
|
+
errors: Array<{ adGroupId: string; adId: string; error: string }>;
|
|
87
|
+
results: Array<{ adGroupId: string; adId: string; response: MutateResponse }>;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// --- Keyword Bulk Operations ---
|
|
91
|
+
|
|
92
|
+
export interface BulkKeywordOptions extends BulkOperationOptions {
|
|
93
|
+
/** Entries with adGroupId and criterionId pairs */
|
|
94
|
+
entries: Array<{ adGroupId: string; criterionId: string }>;
|
|
95
|
+
status: 'ENABLED' | 'PAUSED' | 'REMOVED';
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export interface BulkKeywordResult {
|
|
99
|
+
total: number;
|
|
100
|
+
success: number;
|
|
101
|
+
failed: number;
|
|
102
|
+
errors: Array<{ adGroupId: string; criterionId: string; error: string }>;
|
|
103
|
+
results: Array<{ adGroupId: string; criterionId: string; response: MutateResponse }>;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ============================================
|
|
107
|
+
// Bulk Operations API
|
|
108
|
+
// ============================================
|
|
109
|
+
|
|
110
|
+
export class BulkApi {
|
|
111
|
+
private readonly client: GoogleAdsClient;
|
|
112
|
+
|
|
113
|
+
constructor(client: GoogleAdsClient) {
|
|
114
|
+
this.client = client;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ============================================
|
|
118
|
+
// Bulk Campaign Status Changes
|
|
119
|
+
// ============================================
|
|
120
|
+
|
|
121
|
+
async campaigns(options: BulkCampaignOptions): Promise<BulkCampaignResult> {
|
|
122
|
+
const { campaignIds, status, concurrency = 10, dryRun = false, onProgress, onError } = options;
|
|
123
|
+
|
|
124
|
+
const result: BulkCampaignResult = {
|
|
125
|
+
total: campaignIds.length,
|
|
126
|
+
success: 0,
|
|
127
|
+
failed: 0,
|
|
128
|
+
errors: [],
|
|
129
|
+
results: [],
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
if (campaignIds.length === 0) return result;
|
|
133
|
+
|
|
134
|
+
const chunks = this.chunkArray(campaignIds, concurrency);
|
|
135
|
+
|
|
136
|
+
for (const chunk of chunks) {
|
|
137
|
+
await Promise.all(
|
|
138
|
+
chunk.map(async (campaignId) => {
|
|
139
|
+
try {
|
|
140
|
+
if (dryRun) {
|
|
141
|
+
result.success++;
|
|
142
|
+
} else {
|
|
143
|
+
const operation = status === 'REMOVED'
|
|
144
|
+
? { remove: `customers/${this.client.getCustomerId()}/campaigns/${campaignId}` }
|
|
145
|
+
: {
|
|
146
|
+
update: {
|
|
147
|
+
resourceName: `customers/${this.client.getCustomerId()}/campaigns/${campaignId}`,
|
|
148
|
+
status,
|
|
149
|
+
},
|
|
150
|
+
updateMask: 'status',
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const response = await this.client.mutate('campaigns', [operation]);
|
|
154
|
+
result.success++;
|
|
155
|
+
result.results.push({ campaignId, response });
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (onProgress) onProgress(result.success + result.failed, result.total, campaignId);
|
|
159
|
+
} catch (err) {
|
|
160
|
+
result.failed++;
|
|
161
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
162
|
+
result.errors.push({ campaignId, error: errorMessage });
|
|
163
|
+
if (onError) onError(err instanceof Error ? err : new Error(errorMessage), campaignId);
|
|
164
|
+
}
|
|
165
|
+
})
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return result;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// ============================================
|
|
173
|
+
// Bulk Ad Group Status Changes
|
|
174
|
+
// ============================================
|
|
175
|
+
|
|
176
|
+
async adGroups(options: BulkAdGroupOptions): Promise<BulkAdGroupResult> {
|
|
177
|
+
const { adGroupIds, status, concurrency = 10, dryRun = false, onProgress, onError } = options;
|
|
178
|
+
|
|
179
|
+
const result: BulkAdGroupResult = {
|
|
180
|
+
total: adGroupIds.length,
|
|
181
|
+
success: 0,
|
|
182
|
+
failed: 0,
|
|
183
|
+
errors: [],
|
|
184
|
+
results: [],
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
if (adGroupIds.length === 0) return result;
|
|
188
|
+
|
|
189
|
+
const chunks = this.chunkArray(adGroupIds, concurrency);
|
|
190
|
+
|
|
191
|
+
for (const chunk of chunks) {
|
|
192
|
+
await Promise.all(
|
|
193
|
+
chunk.map(async (adGroupId) => {
|
|
194
|
+
try {
|
|
195
|
+
if (dryRun) {
|
|
196
|
+
result.success++;
|
|
197
|
+
} else {
|
|
198
|
+
const operation = {
|
|
199
|
+
update: {
|
|
200
|
+
resourceName: `customers/${this.client.getCustomerId()}/adGroups/${adGroupId}`,
|
|
201
|
+
status,
|
|
202
|
+
},
|
|
203
|
+
updateMask: 'status',
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
const response = await this.client.mutate('adGroups', [operation]);
|
|
207
|
+
result.success++;
|
|
208
|
+
result.results.push({ adGroupId, response });
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (onProgress) onProgress(result.success + result.failed, result.total, adGroupId);
|
|
212
|
+
} catch (err) {
|
|
213
|
+
result.failed++;
|
|
214
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
215
|
+
result.errors.push({ adGroupId, error: errorMessage });
|
|
216
|
+
if (onError) onError(err instanceof Error ? err : new Error(errorMessage), adGroupId);
|
|
217
|
+
}
|
|
218
|
+
})
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return result;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// ============================================
|
|
226
|
+
// Bulk Ad Status Changes
|
|
227
|
+
// ============================================
|
|
228
|
+
|
|
229
|
+
async ads(options: BulkAdOptions): Promise<BulkAdResult> {
|
|
230
|
+
const { entries, status, concurrency = 10, dryRun = false, onProgress, onError } = options;
|
|
231
|
+
|
|
232
|
+
const result: BulkAdResult = {
|
|
233
|
+
total: entries.length,
|
|
234
|
+
success: 0,
|
|
235
|
+
failed: 0,
|
|
236
|
+
errors: [],
|
|
237
|
+
results: [],
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
if (entries.length === 0) return result;
|
|
241
|
+
|
|
242
|
+
const chunks = this.chunkArray(entries, concurrency);
|
|
243
|
+
|
|
244
|
+
for (const chunk of chunks) {
|
|
245
|
+
await Promise.all(
|
|
246
|
+
chunk.map(async (entry) => {
|
|
247
|
+
try {
|
|
248
|
+
if (dryRun) {
|
|
249
|
+
result.success++;
|
|
250
|
+
} else {
|
|
251
|
+
const operation = status === 'REMOVED'
|
|
252
|
+
? { remove: `customers/${this.client.getCustomerId()}/adGroupAds/${entry.adGroupId}~${entry.adId}` }
|
|
253
|
+
: {
|
|
254
|
+
update: {
|
|
255
|
+
resourceName: `customers/${this.client.getCustomerId()}/adGroupAds/${entry.adGroupId}~${entry.adId}`,
|
|
256
|
+
status,
|
|
257
|
+
},
|
|
258
|
+
updateMask: 'status',
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
const response = await this.client.mutate('adGroupAds', [operation]);
|
|
262
|
+
result.success++;
|
|
263
|
+
result.results.push({ adGroupId: entry.adGroupId, adId: entry.adId, response });
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (onProgress) onProgress(result.success + result.failed, result.total, entry);
|
|
267
|
+
} catch (err) {
|
|
268
|
+
result.failed++;
|
|
269
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
270
|
+
result.errors.push({
|
|
271
|
+
adGroupId: entry.adGroupId,
|
|
272
|
+
adId: entry.adId,
|
|
273
|
+
error: errorMessage,
|
|
274
|
+
});
|
|
275
|
+
if (onError) onError(err instanceof Error ? err : new Error(errorMessage), entry);
|
|
276
|
+
}
|
|
277
|
+
})
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return result;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// ============================================
|
|
285
|
+
// Bulk Keyword Status Changes
|
|
286
|
+
// ============================================
|
|
287
|
+
|
|
288
|
+
async keywords(options: BulkKeywordOptions): Promise<BulkKeywordResult> {
|
|
289
|
+
const { entries, status, concurrency = 10, dryRun = false, onProgress, onError } = options;
|
|
290
|
+
|
|
291
|
+
const result: BulkKeywordResult = {
|
|
292
|
+
total: entries.length,
|
|
293
|
+
success: 0,
|
|
294
|
+
failed: 0,
|
|
295
|
+
errors: [],
|
|
296
|
+
results: [],
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
if (entries.length === 0) return result;
|
|
300
|
+
|
|
301
|
+
const chunks = this.chunkArray(entries, concurrency);
|
|
302
|
+
|
|
303
|
+
for (const chunk of chunks) {
|
|
304
|
+
await Promise.all(
|
|
305
|
+
chunk.map(async (entry) => {
|
|
306
|
+
try {
|
|
307
|
+
if (dryRun) {
|
|
308
|
+
result.success++;
|
|
309
|
+
} else {
|
|
310
|
+
const operation = status === 'REMOVED'
|
|
311
|
+
? { remove: `customers/${this.client.getCustomerId()}/adGroupCriteria/${entry.adGroupId}~${entry.criterionId}` }
|
|
312
|
+
: {
|
|
313
|
+
update: {
|
|
314
|
+
resourceName: `customers/${this.client.getCustomerId()}/adGroupCriteria/${entry.adGroupId}~${entry.criterionId}`,
|
|
315
|
+
status,
|
|
316
|
+
},
|
|
317
|
+
updateMask: 'status',
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
const response = await this.client.mutate('adGroupCriteria', [operation]);
|
|
321
|
+
result.success++;
|
|
322
|
+
result.results.push({ adGroupId: entry.adGroupId, criterionId: entry.criterionId, response });
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (onProgress) onProgress(result.success + result.failed, result.total, entry);
|
|
326
|
+
} catch (err) {
|
|
327
|
+
result.failed++;
|
|
328
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
329
|
+
result.errors.push({
|
|
330
|
+
adGroupId: entry.adGroupId,
|
|
331
|
+
criterionId: entry.criterionId,
|
|
332
|
+
error: errorMessage,
|
|
333
|
+
});
|
|
334
|
+
if (onError) onError(err instanceof Error ? err : new Error(errorMessage), entry);
|
|
335
|
+
}
|
|
336
|
+
})
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
return result;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// ============================================
|
|
344
|
+
// Helper Methods
|
|
345
|
+
// ============================================
|
|
346
|
+
|
|
347
|
+
private chunkArray<T>(array: T[], size: number): T[][] {
|
|
348
|
+
const chunks: T[][] = [];
|
|
349
|
+
for (let i = 0; i < array.length; i += size) {
|
|
350
|
+
chunks.push(array.slice(i, i + size));
|
|
351
|
+
}
|
|
352
|
+
return chunks;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
@@ -4,6 +4,7 @@ import { AdGroupsApi } from './adGroups';
|
|
|
4
4
|
import { AdsApi } from './ads';
|
|
5
5
|
import { KeywordsApi } from './keywords';
|
|
6
6
|
import { ReportsApi } from './reports';
|
|
7
|
+
import { BulkApi } from './bulk';
|
|
7
8
|
import type { GoogleAdsConfig, SearchResponse } from '../types';
|
|
8
9
|
|
|
9
10
|
export class GoogleAds {
|
|
@@ -14,6 +15,7 @@ export class GoogleAds {
|
|
|
14
15
|
public ads: AdsApi;
|
|
15
16
|
public keywords: KeywordsApi;
|
|
16
17
|
public reports: ReportsApi;
|
|
18
|
+
public bulk: BulkApi;
|
|
17
19
|
|
|
18
20
|
constructor(config: GoogleAdsConfig) {
|
|
19
21
|
this.client = new GoogleAdsClient(config);
|
|
@@ -23,6 +25,7 @@ export class GoogleAds {
|
|
|
23
25
|
this.ads = new AdsApi(this.client);
|
|
24
26
|
this.keywords = new KeywordsApi(this.client);
|
|
25
27
|
this.reports = new ReportsApi(this.client);
|
|
28
|
+
this.bulk = new BulkApi(this.client);
|
|
26
29
|
}
|
|
27
30
|
|
|
28
31
|
/**
|
|
@@ -74,3 +77,4 @@ export { AdGroupsApi } from './adGroups';
|
|
|
74
77
|
export { AdsApi } from './ads';
|
|
75
78
|
export { KeywordsApi } from './keywords';
|
|
76
79
|
export { ReportsApi } from './reports';
|
|
80
|
+
export { BulkApi } from './bulk';
|
|
@@ -1252,6 +1252,198 @@ reports
|
|
|
1252
1252
|
}
|
|
1253
1253
|
});
|
|
1254
1254
|
|
|
1255
|
+
// ============================================
|
|
1256
|
+
// Bulk Commands
|
|
1257
|
+
// ============================================
|
|
1258
|
+
|
|
1259
|
+
const bulk = program.command('bulk').description('Bulk operations');
|
|
1260
|
+
|
|
1261
|
+
bulk
|
|
1262
|
+
.command('campaigns')
|
|
1263
|
+
.description('Bulk update campaign status')
|
|
1264
|
+
.requiredOption('--ids <ids>', 'Comma-separated campaign IDs')
|
|
1265
|
+
.requiredOption('--status <status>', 'Target status (ENABLED, PAUSED, REMOVED)')
|
|
1266
|
+
.option('--concurrency <number>', 'Max concurrent requests', '10')
|
|
1267
|
+
.option('--dry-run', 'Preview without making changes')
|
|
1268
|
+
.action(async (opts) => {
|
|
1269
|
+
const campaignIds = opts.ids.split(',').map((id: string) => id.trim()).filter(Boolean);
|
|
1270
|
+
const concurrency = parseInt(opts.concurrency, 10);
|
|
1271
|
+
|
|
1272
|
+
if (campaignIds.length === 0) {
|
|
1273
|
+
error('No campaign IDs provided');
|
|
1274
|
+
process.exit(1);
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
const client = await getClient(program.opts().customer);
|
|
1278
|
+
info(`Updating ${campaignIds.length} campaign(s) to ${opts.status}...`);
|
|
1279
|
+
if (opts.dryRun) info('(dry run)');
|
|
1280
|
+
|
|
1281
|
+
const result = await client.bulk.campaigns({
|
|
1282
|
+
campaignIds,
|
|
1283
|
+
status: opts.status,
|
|
1284
|
+
concurrency,
|
|
1285
|
+
dryRun: opts.dryRun,
|
|
1286
|
+
onProgress: (current, total) => {
|
|
1287
|
+
process.stdout.write(`\rProgress: ${current}/${total}`);
|
|
1288
|
+
},
|
|
1289
|
+
onError: (err, id) => {
|
|
1290
|
+
console.error(`\n Failed: ${id} - ${err.message}`);
|
|
1291
|
+
},
|
|
1292
|
+
});
|
|
1293
|
+
|
|
1294
|
+
console.log();
|
|
1295
|
+
success(`Done: ${result.success} updated, ${result.failed} failed`);
|
|
1296
|
+
if (result.errors.length > 0) {
|
|
1297
|
+
warn('Errors:');
|
|
1298
|
+
for (const e of result.errors) {
|
|
1299
|
+
console.log(` - ${e.campaignId}: ${e.error}`);
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
});
|
|
1303
|
+
|
|
1304
|
+
bulk
|
|
1305
|
+
.command('adgroups')
|
|
1306
|
+
.description('Bulk update ad group status')
|
|
1307
|
+
.requiredOption('--ids <ids>', 'Comma-separated ad group IDs')
|
|
1308
|
+
.requiredOption('--status <status>', 'Target status (ENABLED, PAUSED)')
|
|
1309
|
+
.option('--concurrency <number>', 'Max concurrent requests', '10')
|
|
1310
|
+
.option('--dry-run', 'Preview without making changes')
|
|
1311
|
+
.action(async (opts) => {
|
|
1312
|
+
const adGroupIds = opts.ids.split(',').map((id: string) => id.trim()).filter(Boolean);
|
|
1313
|
+
const concurrency = parseInt(opts.concurrency, 10);
|
|
1314
|
+
|
|
1315
|
+
if (adGroupIds.length === 0) {
|
|
1316
|
+
error('No ad group IDs provided');
|
|
1317
|
+
process.exit(1);
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
const client = await getClient(program.opts().customer);
|
|
1321
|
+
info(`Updating ${adGroupIds.length} ad group(s) to ${opts.status}...`);
|
|
1322
|
+
if (opts.dryRun) info('(dry run)');
|
|
1323
|
+
|
|
1324
|
+
const result = await client.bulk.adGroups({
|
|
1325
|
+
adGroupIds,
|
|
1326
|
+
status: opts.status,
|
|
1327
|
+
concurrency,
|
|
1328
|
+
dryRun: opts.dryRun,
|
|
1329
|
+
onProgress: (current, total) => {
|
|
1330
|
+
process.stdout.write(`\rProgress: ${current}/${total}`);
|
|
1331
|
+
},
|
|
1332
|
+
onError: (err, id) => {
|
|
1333
|
+
console.error(`\n Failed: ${id} - ${err.message}`);
|
|
1334
|
+
},
|
|
1335
|
+
});
|
|
1336
|
+
|
|
1337
|
+
console.log();
|
|
1338
|
+
success(`Done: ${result.success} updated, ${result.failed} failed`);
|
|
1339
|
+
if (result.errors.length > 0) {
|
|
1340
|
+
warn('Errors:');
|
|
1341
|
+
for (const e of result.errors) {
|
|
1342
|
+
console.log(` - ${e.adGroupId}: ${e.error}`);
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
});
|
|
1346
|
+
|
|
1347
|
+
bulk
|
|
1348
|
+
.command('ads')
|
|
1349
|
+
.description('Bulk update ad status')
|
|
1350
|
+
.requiredOption('--entries <entries>', 'JSON array of {adGroupId, adId}')
|
|
1351
|
+
.requiredOption('--status <status>', 'Target status (ENABLED, PAUSED, REMOVED)')
|
|
1352
|
+
.option('--concurrency <number>', 'Max concurrent requests', '10')
|
|
1353
|
+
.option('--dry-run', 'Preview without making changes')
|
|
1354
|
+
.action(async (opts) => {
|
|
1355
|
+
let entries: Array<{ adGroupId: string; adId: string }>;
|
|
1356
|
+
try {
|
|
1357
|
+
entries = JSON.parse(opts.entries) as Array<{ adGroupId: string; adId: string }>;
|
|
1358
|
+
} catch {
|
|
1359
|
+
error('Invalid JSON for --entries');
|
|
1360
|
+
process.exit(1);
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
if (entries.length === 0) {
|
|
1364
|
+
error('No entries provided');
|
|
1365
|
+
process.exit(1);
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
const client = await getClient(program.opts().customer);
|
|
1369
|
+
const concurrency = parseInt(opts.concurrency, 10);
|
|
1370
|
+
info(`Updating ${entries.length} ad(s)...`);
|
|
1371
|
+
if (opts.dryRun) info('(dry run)');
|
|
1372
|
+
|
|
1373
|
+
const result = await client.bulk.ads({
|
|
1374
|
+
entries,
|
|
1375
|
+
status: opts.status,
|
|
1376
|
+
concurrency,
|
|
1377
|
+
dryRun: opts.dryRun,
|
|
1378
|
+
onProgress: (current, total) => {
|
|
1379
|
+
process.stdout.write(`\rProgress: ${current}/${total}`);
|
|
1380
|
+
},
|
|
1381
|
+
onError: (err, entry) => {
|
|
1382
|
+
const e = entry as { adGroupId: string; adId: string };
|
|
1383
|
+
console.error(`\n Failed: ${e.adGroupId}/${e.adId} - ${err.message}`);
|
|
1384
|
+
},
|
|
1385
|
+
});
|
|
1386
|
+
|
|
1387
|
+
console.log();
|
|
1388
|
+
success(`Done: ${result.success} updated, ${result.failed} failed`);
|
|
1389
|
+
if (result.errors.length > 0) {
|
|
1390
|
+
warn('Errors:');
|
|
1391
|
+
for (const e of result.errors) {
|
|
1392
|
+
console.log(` - ${e.adGroupId}/${e.adId}: ${e.error}`);
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
});
|
|
1396
|
+
|
|
1397
|
+
bulk
|
|
1398
|
+
.command('keywords')
|
|
1399
|
+
.description('Bulk update keyword status')
|
|
1400
|
+
.requiredOption('--entries <entries>', 'JSON array of {adGroupId, criterionId}')
|
|
1401
|
+
.requiredOption('--status <status>', 'Target status (ENABLED, PAUSED, REMOVED)')
|
|
1402
|
+
.option('--concurrency <number>', 'Max concurrent requests', '10')
|
|
1403
|
+
.option('--dry-run', 'Preview without making changes')
|
|
1404
|
+
.action(async (opts) => {
|
|
1405
|
+
let entries: Array<{ adGroupId: string; criterionId: string }>;
|
|
1406
|
+
try {
|
|
1407
|
+
entries = JSON.parse(opts.entries) as Array<{ adGroupId: string; criterionId: string }>;
|
|
1408
|
+
} catch {
|
|
1409
|
+
error('Invalid JSON for --entries');
|
|
1410
|
+
process.exit(1);
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
if (entries.length === 0) {
|
|
1414
|
+
error('No entries provided');
|
|
1415
|
+
process.exit(1);
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
const client = await getClient(program.opts().customer);
|
|
1419
|
+
const concurrency = parseInt(opts.concurrency, 10);
|
|
1420
|
+
info(`Updating ${entries.length} keyword(s)...`);
|
|
1421
|
+
if (opts.dryRun) info('(dry run)');
|
|
1422
|
+
|
|
1423
|
+
const result = await client.bulk.keywords({
|
|
1424
|
+
entries,
|
|
1425
|
+
status: opts.status,
|
|
1426
|
+
concurrency,
|
|
1427
|
+
dryRun: opts.dryRun,
|
|
1428
|
+
onProgress: (current, total) => {
|
|
1429
|
+
process.stdout.write(`\rProgress: ${current}/${total}`);
|
|
1430
|
+
},
|
|
1431
|
+
onError: (err, entry) => {
|
|
1432
|
+
const e = entry as { adGroupId: string; criterionId: string };
|
|
1433
|
+
console.error(`\n Failed: ${e.adGroupId}/${e.criterionId} - ${err.message}`);
|
|
1434
|
+
},
|
|
1435
|
+
});
|
|
1436
|
+
|
|
1437
|
+
console.log();
|
|
1438
|
+
success(`Done: ${result.success} updated, ${result.failed} failed`);
|
|
1439
|
+
if (result.errors.length > 0) {
|
|
1440
|
+
warn('Errors:');
|
|
1441
|
+
for (const e of result.errors) {
|
|
1442
|
+
console.log(` - ${e.adGroupId}/${e.criterionId}: ${e.error}`);
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
});
|
|
1446
|
+
|
|
1255
1447
|
// ============================================
|
|
1256
1448
|
// Parse and Execute
|
|
1257
1449
|
// ============================================
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { GoogleCalendarClient } from './client';
|
|
2
|
+
import type { CalendarAcl, CalendarAclListResponse, CalendarAclInsert } from '../types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Calendar ACL API module - manage calendar access control lists
|
|
6
|
+
*/
|
|
7
|
+
export class AclApi {
|
|
8
|
+
constructor(private readonly client: GoogleCalendarClient) {}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* List ACL rules for a calendar
|
|
12
|
+
*/
|
|
13
|
+
async list(calendarId: string, options: { maxResults?: number; pageToken?: string } = {}): Promise<CalendarAclListResponse> {
|
|
14
|
+
const params: Record<string, number | string | undefined> = {};
|
|
15
|
+
if (options.maxResults) params.maxResults = options.maxResults;
|
|
16
|
+
if (options.pageToken) params.pageToken = options.pageToken;
|
|
17
|
+
return this.client.get<CalendarAclListResponse>(`/calendars/${encodeURIComponent(calendarId)}/acl`, params);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Iterate over all ACL rules (handles pagination)
|
|
22
|
+
*/
|
|
23
|
+
async *listAll(calendarId: string): AsyncGenerator<CalendarAcl> {
|
|
24
|
+
let nextPageToken: string | undefined;
|
|
25
|
+
do {
|
|
26
|
+
const result = await this.list(calendarId, { pageToken: nextPageToken });
|
|
27
|
+
for (const rule of result.items) {
|
|
28
|
+
yield rule;
|
|
29
|
+
}
|
|
30
|
+
nextPageToken = result.nextPageToken;
|
|
31
|
+
} while (nextPageToken);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Get a specific ACL rule
|
|
36
|
+
*/
|
|
37
|
+
async get(calendarId: string, ruleId: string): Promise<CalendarAcl> {
|
|
38
|
+
return this.client.get<CalendarAcl>(`/calendars/${encodeURIComponent(calendarId)}/acl/${encodeURIComponent(ruleId)}`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Insert a new ACL rule
|
|
43
|
+
*/
|
|
44
|
+
async insert(calendarId: string, rule: CalendarAclInsert): Promise<CalendarAcl> {
|
|
45
|
+
return this.client.post<CalendarAcl>(`/calendars/${encodeURIComponent(calendarId)}/acl`, {
|
|
46
|
+
role: rule.role,
|
|
47
|
+
scope: rule.scope,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Delete an ACL rule
|
|
53
|
+
*/
|
|
54
|
+
async delete(calendarId: string, ruleId: string): Promise<void> {
|
|
55
|
+
await this.client.delete<void>(`/calendars/${encodeURIComponent(calendarId)}/acl/${encodeURIComponent(ruleId)}`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Update an ACL rule
|
|
60
|
+
*/
|
|
61
|
+
async update(calendarId: string, ruleId: string, role: CalendarAclInsert['role']): Promise<CalendarAcl> {
|
|
62
|
+
return this.client.patch<CalendarAcl>(`/calendars/${encodeURIComponent(calendarId)}/acl/${encodeURIComponent(ruleId)}`, {
|
|
63
|
+
role,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|