@pipedream/google_drive 0.3.3 → 0.4.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 (41) hide show
  1. package/actions/add-file-sharing-preference/add-file-sharing-preference.mjs +83 -0
  2. package/actions/copy-file/copy-file.mjs +34 -0
  3. package/actions/create-file/create-file.mjs +242 -0
  4. package/actions/create-file-from-template/create-file-from-template.mjs +98 -0
  5. package/actions/create-file-from-text/create-file-from-text.mjs +67 -0
  6. package/actions/create-folder/create-folder.mjs +54 -0
  7. package/actions/create-shared-drive/create-shared-drive.mjs +25 -0
  8. package/actions/delete-file/delete-file.mjs +37 -0
  9. package/actions/delete-shared-drive/delete-shared-drive.mjs +30 -0
  10. package/actions/download-file/download-file.mjs +120 -0
  11. package/actions/find-file/find-file.mjs +35 -0
  12. package/actions/find-folder/find-folder.mjs +38 -0
  13. package/actions/get-folder-id-for-path/get-folder-id-for-path.mjs +62 -0
  14. package/actions/get-shared-drive/get-shared-drive.mjs +37 -0
  15. package/actions/google-mime-types.mjs +19 -0
  16. package/actions/google-workspace-export-formats.mjs +74 -0
  17. package/actions/language-codes.mjs +742 -0
  18. package/actions/move-file/move-file.mjs +52 -0
  19. package/actions/move-file-to-trash/move-file-to-trash.mjs +41 -0
  20. package/actions/replace-file/replace-file.mjs +90 -0
  21. package/actions/search-shared-drives/search-shared-drives.mjs +34 -0
  22. package/actions/update-file/update-file.mjs +164 -0
  23. package/actions/update-shared-drive/update-shared-drive.mjs +77 -0
  24. package/actions/upload-file/upload-file.mjs +89 -0
  25. package/constants.mjs +190 -0
  26. package/google_drive.app.mjs +1429 -0
  27. package/package.json +23 -20
  28. package/pnpm-lock.yaml +393 -0
  29. package/sources/changes-to-specific-files/changes-to-specific-files.mjs +226 -0
  30. package/sources/changes-to-specific-files-shared-drive/changes-to-specific-files-shared-drive.mjs +110 -0
  31. package/sources/common-webhook.mjs +201 -0
  32. package/sources/new-files-instant/new-files-instant.mjs +95 -0
  33. package/sources/new-or-modified-comments/new-or-modified-comments.mjs +104 -0
  34. package/sources/new-or-modified-files/new-or-modified-files.mjs +66 -0
  35. package/sources/new-or-modified-folders/new-or-modified-folders.mjs +86 -0
  36. package/sources/new-shared-drive/new-shared-drive.mjs +68 -0
  37. package/utils.mjs +247 -0
  38. package/LICENSE +0 -7
  39. package/google_drive.app.js +0 -212
  40. package/sources/changes-to-specific-files/changes-to-specific-files.js +0 -226
  41. package/sources/new-or-modified-files/new-or-modified-files.js +0 -213
@@ -0,0 +1,1429 @@
1
+ import axios from "axios";
2
+ import drive from "@googleapis/drive";
3
+ import { v4 as uuid } from "uuid";
4
+ import isoLanguages from "./actions/language-codes.mjs";
5
+ import mimeDb from "mime-db";
6
+ const mimeTypes = Object.keys(mimeDb);
7
+
8
+ import {
9
+ GOOGLE_DRIVE_UPDATE_TYPES,
10
+ MY_DRIVE_VALUE,
11
+ WEBHOOK_SUBSCRIPTION_EXPIRATION_TIME_MILLISECONDS,
12
+ GOOGLE_DRIVE_FOLDER_MIME_TYPE,
13
+ GOOGLE_DRIVE_ROLES,
14
+ GOOGLE_DRIVE_GRANTEE_TYPES,
15
+ GOOGLE_DRIVE_GRANTEE_ANYONE,
16
+ GOOGLE_DRIVE_ROLE_READER,
17
+ } from "./constants.mjs";
18
+ import googleMimeTypes from "./actions/google-mime-types.mjs";
19
+
20
+ import {
21
+ isMyDrive,
22
+ getDriveId,
23
+ getListFilesOpts,
24
+ omitEmptyStringValues,
25
+ toSingleLineString,
26
+ getFilePaths,
27
+ } from "./utils.mjs";
28
+
29
+ export default {
30
+ type: "app",
31
+ app: "google_drive",
32
+ propDefinitions: {
33
+ watchedDrive: {
34
+ type: "string",
35
+ label: "Drive",
36
+ description: "Defaults to `My Drive`. To select a [Shared Drive](https://support.google.com/a/users/answer/9310351) instead, select it from this list.",
37
+ optional: true,
38
+ default: MY_DRIVE_VALUE,
39
+ async options({ prevContext }) {
40
+ const { nextPageToken } = prevContext;
41
+ return this._listDriveOptions(nextPageToken);
42
+ },
43
+ },
44
+ folderId: {
45
+ type: "string",
46
+ label: "Folder",
47
+ description: "The folder in the drive",
48
+ async options({
49
+ prevContext,
50
+ drive,
51
+ baseOpts = {
52
+ q: `mimeType = '${GOOGLE_DRIVE_FOLDER_MIME_TYPE}'`,
53
+ },
54
+ }) {
55
+ const { nextPageToken } = prevContext;
56
+ return this.listDriveFilesOptions(drive, nextPageToken, baseOpts);
57
+ },
58
+ },
59
+ fileId: {
60
+ type: "string",
61
+ label: "File",
62
+ description: "The file in the drive",
63
+ async options({
64
+ prevContext,
65
+ drive,
66
+ baseOpts = {
67
+ q: `mimeType != '${GOOGLE_DRIVE_FOLDER_MIME_TYPE}'`,
68
+ },
69
+ }) {
70
+ const { nextPageToken } = prevContext;
71
+ return this.listDriveFilesOptions(drive, nextPageToken, baseOpts);
72
+ },
73
+ },
74
+ fileOrFolderId: {
75
+ type: "string",
76
+ label: "File or Folder",
77
+ description: "The file or folder in the drive",
78
+ async options({
79
+ prevContext, drive, baseOpts = {},
80
+ }) {
81
+ const { nextPageToken } = prevContext;
82
+ return this.listDriveFilesOptions(drive, nextPageToken, baseOpts);
83
+ },
84
+ },
85
+ fileParents: {
86
+ type: "string[]",
87
+ label: "File Parents",
88
+ description: "The folder IDs of the file's parents",
89
+ optional: true,
90
+ async options({ fileId }) {
91
+ if (!fileId) {
92
+ return [];
93
+ }
94
+ let file;
95
+ try {
96
+ file = await this.getFile(fileId, {
97
+ fields: "parents",
98
+ });
99
+ } catch (err) {
100
+ return [];
101
+ }
102
+ let parentFolders = await Promise.all(
103
+ file.parents.map((parentId) => this.getFile(parentId, {
104
+ fields: "id,name",
105
+ })),
106
+ );
107
+ return parentFolders.map(({
108
+ id, name,
109
+ }) => ({
110
+ value: id,
111
+ label: name,
112
+ }));
113
+ },
114
+ },
115
+ updateTypes: {
116
+ type: "string[]",
117
+ label: "Types of updates",
118
+ description: `The types of updates you want to watch for on these files.
119
+ [See Google's docs]
120
+ (https://developers.google.com/drive/api/v3/push#understanding-drive-api-notification-events).`,
121
+ default: GOOGLE_DRIVE_UPDATE_TYPES,
122
+ options: GOOGLE_DRIVE_UPDATE_TYPES,
123
+ },
124
+ watchForPropertiesChanges: {
125
+ type: "boolean",
126
+ label: "Watch for changes to file properties",
127
+ description: `Watch for changes to [file properties](https://developers.google.com/drive/api/v3/properties)
128
+ in addition to changes to content. **Defaults to \`false\`, watching for only changes to content**.`,
129
+ optional: true,
130
+ default: false,
131
+ },
132
+ fileUrl: {
133
+ type: "string",
134
+ label: "File URL",
135
+ description: toSingleLineString(`
136
+ The URL of the file you want to upload to Google Drive. Must specify either **File URL**
137
+ or **File Path**.
138
+ `),
139
+ optional: true,
140
+ },
141
+ filePath: {
142
+ type: "string",
143
+ label: "File Path",
144
+ description: toSingleLineString(`
145
+ The path to the file saved to the [\`/tmp\`
146
+ directory](https://pipedream.com/docs/workflows/steps/code/nodejs/working-with-files/#the-tmp-directory)
147
+ (e.g. \`/tmp/myFile.csv\`). Must specify either **File URL** or **File Path**.
148
+ `),
149
+ optional: true,
150
+ },
151
+ fileName: {
152
+ type: "string",
153
+ label: "Name",
154
+ description: "The name of the file (e.g., `/myFile.csv`)",
155
+ optional: true,
156
+ },
157
+ fileNameSearchTerm: {
158
+ type: "string",
159
+ label: "Search Name",
160
+ description: "Enter the name of a file to search for.",
161
+ optional: true,
162
+ },
163
+ mimeType: {
164
+ type: "string",
165
+ label: "Mime Type",
166
+ description: toSingleLineString(`
167
+ The MIME type of the file (e.g., \`image/jpeg\`). Google Drive will attempt to automatically
168
+ detect an appropriate value from uploaded content if no value is provided. The value cannot
169
+ be changed unless a new revision is uploaded. If a file is created with a [Google Doc MIME
170
+ type](https://developers.google.com/drive/api/v3/mime-types), the uploaded content will be
171
+ imported if possible.
172
+ `),
173
+ optional: true,
174
+ async options({ page = 0 }) {
175
+ const allTypes = googleMimeTypes.concat(mimeTypes);
176
+ const start = page * 500;
177
+ const end = start + 500;
178
+ return allTypes.slice(start, end);
179
+ },
180
+ },
181
+ uploadType: {
182
+ type: "string",
183
+ label: "Upload Type",
184
+ description: `The type of upload request to the /upload URI. If you are uploading data
185
+ (using an /upload URI), this field is required. If you are creating a metadata-only file,
186
+ this field is not required.
187
+ media - Simple upload. Upload the media only, without any metadata.
188
+ multipart - Multipart upload. Upload both the media and its metadata, in a single request.
189
+ resumable - Resumable upload. Upload the file in a resumable fashion, using a series of
190
+ at least two requests where the first request includes the metadata.`,
191
+ options: [
192
+ "media",
193
+ "multipart",
194
+ "resumable",
195
+ ],
196
+ },
197
+ useDomainAdminAccess: {
198
+ type: "boolean",
199
+ label: "Use Domain Admin Access",
200
+ description: "Issue the request as a domain administrator",
201
+ optional: true,
202
+ default: false,
203
+ },
204
+ role: {
205
+ type: "string",
206
+ label: "Role",
207
+ description: "The role granted by this permission",
208
+ optional: true,
209
+ default: GOOGLE_DRIVE_ROLE_READER,
210
+ options: GOOGLE_DRIVE_ROLES,
211
+ },
212
+ type: {
213
+ type: "string",
214
+ label: "Type",
215
+ description:
216
+ "The type of the grantee. If **Type** is `user` or `group`, you must provide an **Email Address** for the user or group. When **Type** is `domain`, you must provide a `Domain`. Sharing with a domain is only valid for G Suite users.",
217
+ optional: true,
218
+ default: GOOGLE_DRIVE_GRANTEE_ANYONE,
219
+ options: GOOGLE_DRIVE_GRANTEE_TYPES,
220
+ },
221
+ domain: {
222
+ type: "string",
223
+ label: "Domain",
224
+ description:
225
+ "The domain of the G Suite organization to which this permission refers if **Type** is `domain` (e.g., `yourcomapany.com`)",
226
+ optional: true,
227
+ },
228
+ emailAddress: {
229
+ type: "string",
230
+ label: "Email Address",
231
+ description:
232
+ "The email address of the user or group to which this permission refers if **Type** is `user` or `group`",
233
+ optional: true,
234
+ },
235
+ ocrLanguage: {
236
+ type: "string",
237
+ label: "OCR Language",
238
+ description:
239
+ "A language hint for OCR processing during image import (ISO 639-1 code)",
240
+ optional: true,
241
+ options: isoLanguages,
242
+ },
243
+ useContentAsIndexableText: {
244
+ type: "boolean",
245
+ label: "Use Content As Indexable Text",
246
+ description:
247
+ "Whether to use the uploaded content as indexable text",
248
+ optional: true,
249
+ },
250
+ keepRevisionForever: {
251
+ type: "boolean",
252
+ label: "Keep Revision Forever",
253
+ description: toSingleLineString(`
254
+ Whether to set the 'keepForever' field in the new head revision. This is only applicable
255
+ to files with binary content in Google Drive. Only 200 revisions for the file can be kept
256
+ forever. If the limit is reached, try deleting pinned revisions.
257
+ `),
258
+ optional: true,
259
+ },
260
+ },
261
+ methods: {
262
+ // Static methods
263
+ isMyDrive,
264
+ getDriveId,
265
+ getFilePaths,
266
+
267
+ // Returns a drive object authenticated with the user's access token
268
+ drive() {
269
+ const auth = new drive.auth.OAuth2();
270
+ auth.setCredentials({
271
+ access_token: this.$auth.oauth_access_token,
272
+ });
273
+ return drive.drive({
274
+ version: "v3",
275
+ auth,
276
+ });
277
+ },
278
+ // Google's push notifications provide a URL to the resource that changed,
279
+ // which we can use to fetch the file's metadata. So we use axios here
280
+ // (vs. the Node client) to get that.
281
+ async getFileMetadata(url) {
282
+ return (
283
+ await axios({
284
+ method: "GET",
285
+ headers: {
286
+ Authorization: `Bearer ${this.$auth.oauth_access_token}`,
287
+ },
288
+ url,
289
+ })
290
+ ).data;
291
+ },
292
+ /**
293
+ * This method yields a list of changes that occurred to files in a
294
+ * particular Google Drive. It is a wrapper around [the `drive.changes.list`
295
+ * API](https://bit.ly/2SGb5M2) but defined as a generator to enable lazy
296
+ * loading of multiple pages.
297
+ *
298
+ * @typedef {object} ChangesPage
299
+ * @property {object[]} changedFiles - the list of file changes, as [defined
300
+ * by the API](https://bit.ly/3h7WeUa). This list filters out any result
301
+ * that is not a proper object.
302
+ * @property {string} nextPageToken - the page token [returned by the last API
303
+ * call](https://bit.ly/3h7WeUa). **Note that this generator keeps track of
304
+ * this token internally, and the purpose of this value is to provide a way
305
+ * for consumers of this method to handle checkpoints in case of an
306
+ * unexpected halt.**
307
+ *
308
+ * @param {string} [pageToken] - the token for continuing a previous list
309
+ * request on the next page. This must be a token that was previously
310
+ * returned by this same method.
311
+ * @param {string} [driveId] - the shared drive from which changes are
312
+ * returned
313
+ * @yields
314
+ * @type {ChangesPage}
315
+ */
316
+ async *listChanges(pageToken, driveId) {
317
+ const drive = this.drive();
318
+ let changeRequest = {
319
+ pageToken,
320
+ pageSize: 1000,
321
+ };
322
+
323
+ // As with many of the methods for Google Drive, we must
324
+ // pass a request of a different shape when we're requesting
325
+ // changes for My Drive (null driveId) vs. a shared drive
326
+ if (driveId) {
327
+ changeRequest = {
328
+ ...changeRequest,
329
+ driveId,
330
+ includeItemsFromAllDrives: true,
331
+ supportsAllDrives: true,
332
+ };
333
+ }
334
+
335
+ while (true) {
336
+ const { data } = await drive.changes.list(changeRequest);
337
+ const {
338
+ changes = [],
339
+ newStartPageToken,
340
+ nextPageToken,
341
+ } = data;
342
+
343
+ // Some changes do not include an associated file object. Return only
344
+ // those that do
345
+ const changedFiles = changes
346
+ .map((change) => change.file)
347
+ .filter((f) => typeof f === "object");
348
+
349
+ yield {
350
+ changedFiles,
351
+ nextPageToken: nextPageToken || newStartPageToken,
352
+ };
353
+
354
+ if (newStartPageToken) {
355
+ // The 'newStartPageToken' field is only returned as part of the last
356
+ // page from the API response: https://bit.ly/3jBEvWV
357
+ break;
358
+ }
359
+
360
+ changeRequest.pageToken = nextPageToken;
361
+ }
362
+ },
363
+ async getPageToken(driveId) {
364
+ const drive = this.drive();
365
+ const request = driveId
366
+ ? {
367
+ driveId,
368
+ supportsAllDrives: true,
369
+ }
370
+ : {};
371
+ const { data } = await drive.changes.getStartPageToken(request);
372
+ return data.startPageToken;
373
+ },
374
+ checkHeaders(headers, subscription, channelID) {
375
+ if (
376
+ !headers["x-goog-resource-state"] ||
377
+ !headers["x-goog-resource-id"] ||
378
+ !headers["x-goog-resource-uri"] ||
379
+ !headers["x-goog-message-number"]
380
+ ) {
381
+ console.log("Request missing necessary headers: ", headers);
382
+ return false;
383
+ }
384
+
385
+ const incomingChannelID = headers["x-goog-channel-id"];
386
+ if (incomingChannelID !== channelID) {
387
+ console.log(
388
+ `Channel ID of ${incomingChannelID} not equal to deployed component channel of ${channelID}`,
389
+ );
390
+ return false;
391
+ }
392
+
393
+ if (headers["x-goog-resource-id"] !== subscription.resourceId) {
394
+ console.log(
395
+ `Resource ID of ${subscription.resourceId} not currently being tracked. Exiting`,
396
+ );
397
+ return false;
398
+ }
399
+ return true;
400
+ },
401
+
402
+ /**
403
+ * A utility method around [the `drive.drives.list`
404
+ * API](https://bit.ly/3AiWE1x) but scoped to a specific page of the API
405
+ * response
406
+ *
407
+ * @typedef {object} DriveListPage - an object representing a page that
408
+ * lists GDrive drives, as defined by [the API](https://bit.ly/3jwxbvy)
409
+ *
410
+ * @param {string} [pageToken] - the page token for the next page of shared
411
+ * drives
412
+ * @param {number} [pageSize=10] - the number of records to retrieve as part
413
+ * of the page
414
+ *
415
+ * @returns
416
+ * @type {DriveListPage}
417
+ */
418
+ async listDrivesInPage(pageToken, pageSize = 10) {
419
+ const drive = this.drive();
420
+ const { data } = await drive.drives.list({
421
+ pageSize,
422
+ pageToken,
423
+ });
424
+ return data;
425
+ },
426
+ /**
427
+ * This method yields the visible GDrive drives of the authenticated
428
+ * account. It is a wrapper around [the `drive.drives.list`
429
+ * API](https://bit.ly/3AiWE1x) but defined as a generator to enable lazy
430
+ * loading of multiple pages.
431
+ *
432
+ * @typedef {object} Drive - an object representing a GDrive drive, as
433
+ * defined by [the API](https://bit.ly/3ycifGY)
434
+ *
435
+ * @yields
436
+ * @type {Drive}
437
+ */
438
+ async *listDrives() {
439
+ let pageToken;
440
+
441
+ while (true) {
442
+ const {
443
+ drives = [],
444
+ nextPageToken,
445
+ } = await this.listDrivesInPage(
446
+ pageToken,
447
+ );
448
+
449
+ for (const drive in drives) {
450
+ yield drive;
451
+ }
452
+
453
+ if (!nextPageToken) {
454
+ // The 'nextPageToken' field is only returned when there's still
455
+ // comments to be retrieved (i.e. when the end of the list has not
456
+ // been reached yet): https://bit.ly/3jwxbvy
457
+ break;
458
+ }
459
+
460
+ pageToken = nextPageToken;
461
+ }
462
+ },
463
+ async _listDriveOptions(pageToken) {
464
+ const {
465
+ drives,
466
+ nextPageToken,
467
+ } = await this.listDrivesInPage(pageToken);
468
+
469
+ // "My Drive" isn't returned from the list of drives, so we add it to the
470
+ // list and assign it a static ID that we can refer to when we need. We
471
+ // only do this during the first page of options (i.e. when `pageToken` is
472
+ // undefined).
473
+ const options =
474
+ pageToken !== undefined
475
+ ? []
476
+ : [
477
+ {
478
+ label: "My Drive",
479
+ value: MY_DRIVE_VALUE,
480
+ },
481
+ ];
482
+ for (const d of drives) {
483
+ options.push({
484
+ label: d.name,
485
+ value: d.id,
486
+ });
487
+ }
488
+ return {
489
+ options,
490
+ context: {
491
+ nextPageToken,
492
+ },
493
+ };
494
+ },
495
+ /**
496
+ * A utility method around [the `drive.files.list`
497
+ * API](https://bit.ly/366CFVN) but scoped to a specific page of the API
498
+ * response
499
+ *
500
+ * @typedef {object} FileListPage - an object representing a page that lists
501
+ * GDrive files, as defined by [the API](https://bit.ly/3xdbAwc)
502
+ *
503
+ * @param {string} [pageToken] - the page token for the next page of shared
504
+ * drives
505
+ * @param {object} [extraOpts = {}] - an object containing extra/optional
506
+ * parameters to be fed to the GDrive API call, as defined in [the API
507
+ * docs](https://bit.ly/3AnQDR1)
508
+ *
509
+ * @returns
510
+ * @type {FileListPage}
511
+ */
512
+ async listFilesInPage(pageToken, extraOpts = {}) {
513
+ const drive = this.drive();
514
+ const { data } = await drive.files.list({
515
+ pageToken,
516
+ ...extraOpts,
517
+ });
518
+ return data;
519
+ },
520
+ /**
521
+ * A utility method around [the `drive.files.list`
522
+ * API](https://bit.ly/366CFVN) but scoped to a specific page of the API
523
+ * response, and intended to be used as a way for prop definitions to return
524
+ * a list of options.
525
+ *
526
+ * @param {string} [pageToken] - the page token for the next page of shared
527
+ * drives
528
+ * @param {object} [extraOpts = {}] - an object containing extra/optional
529
+ * parameters to be fed to the GDrive API call, as defined in [the API
530
+ * docs](https://bit.ly/3AnQDR1)
531
+ *
532
+ * @returns a list of prop options
533
+ */
534
+ async listFilesOptions(pageToken, extraOpts = {}) {
535
+ const {
536
+ files,
537
+ nextPageToken,
538
+ } = await this.listFilesInPage(
539
+ pageToken,
540
+ extraOpts,
541
+ );
542
+ const options = files.map((file) => ({
543
+ label: file.name,
544
+ value: file.id,
545
+ }));
546
+ return {
547
+ options,
548
+ context: {
549
+ nextPageToken,
550
+ },
551
+ };
552
+ },
553
+ /**
554
+ * Method returns a list of folder options
555
+ *
556
+ * @param {string} [pageToken] - the page token for the next page of shared
557
+ * drives
558
+ * @param {object} [opts = {}] - an object containing extra/optional
559
+ * parameters to be fed to the GDrive API call, as defined in [the API
560
+ * docs](https://bit.ly/3AnQDR1)
561
+ *
562
+ * @returns a list of prop options
563
+ */
564
+
565
+ async listFolderOptions(pageToken, opts = {}) {
566
+ return await this.listFilesOptions(pageToken, {
567
+ ...opts,
568
+ q: "mimeType = 'application/vnd.google-apps.folder'",
569
+ });
570
+ },
571
+ /**
572
+ * Gets a list of prop options for a GDrive file in a particular GDrive
573
+ * drive, if provided
574
+ *
575
+ * @param {String} [drive] the ID value of a Google Drive, as provided by the
576
+ * `drive` prop definition of this app, to which the file belongs
577
+ * @param {string} [pageToken] - the page token for the next page of files
578
+ * @param {object} [extraOpts = {}] - an object containing extra/optional
579
+ * parameters to be fed to the GDrive API call, as defined in [the API
580
+ * docs](https://bit.ly/3AnQDR1)
581
+ *
582
+ * @returns a list of prop options
583
+ */
584
+ async listDriveFilesOptions(drive, pageToken = null, extraOpts = {}) {
585
+ const opts = {
586
+ ...getListFilesOpts(drive, extraOpts),
587
+ fields: "nextPageToken,files(id,name,parents)",
588
+ };
589
+ // Fetch folders to use to build file paths. If num folders in a drive exceeds 1000, file
590
+ // paths may be incomplete.
591
+ const foldersPromise = this.listFilesInPage(null, {
592
+ ...opts,
593
+ pageSize: 1000, // Max pageSize
594
+ q: `mimeType = '${GOOGLE_DRIVE_FOLDER_MIME_TYPE}'`,
595
+ });
596
+ const filesPromise = this.listFilesInPage(
597
+ pageToken,
598
+ opts,
599
+ );
600
+ const [
601
+ { files: folders },
602
+ {
603
+ files, nextPageToken,
604
+ },
605
+ ] = await Promise.all([
606
+ foldersPromise,
607
+ filesPromise,
608
+ ]);
609
+ const filePaths = this.getFilePaths(files, folders);
610
+ const options = files.map((file) => ({
611
+ label: filePaths[file.id],
612
+ value: file.id,
613
+ }));
614
+ return {
615
+ options,
616
+ context: {
617
+ nextPageToken,
618
+ },
619
+ };
620
+ },
621
+ /**
622
+ * This method yields comments made to a particular GDrive file. It is a
623
+ * wrapper around [the `drive.comments.list` API](https://bit.ly/2UjYajv)
624
+ * but defined as a generator to enable lazy loading of multiple pages.
625
+ *
626
+ * @typedef {object} Comment - an object representing a comment in a GDrive
627
+ * file, as defined by [the API](https://bit.ly/3htAd12)
628
+ *
629
+ * @yields
630
+ * @type {Comment}
631
+ */
632
+ async *listComments(fileId, startModifiedTime = null) {
633
+ const drive = this.drive();
634
+ const opts = {
635
+ fileId,
636
+ fields: "*",
637
+ pageSize: 100,
638
+ };
639
+
640
+ if (startModifiedTime !== null) {
641
+ opts.startModifiedTime = new Date(startModifiedTime).toISOString();
642
+ }
643
+
644
+ while (true) {
645
+ const { data } = await drive.comments.list(opts);
646
+ const {
647
+ comments = [],
648
+ nextPageToken,
649
+ } = data;
650
+
651
+ for (const comment of comments) {
652
+ yield comment;
653
+ }
654
+
655
+ if (!nextPageToken) {
656
+ // The 'nextPageToken' field is only returned when there's still
657
+ // comments to be retrieved (i.e. when the end of the list has not
658
+ // been reached yet): https://bit.ly/3w9ru9m
659
+ break;
660
+ }
661
+
662
+ opts.pageToken = nextPageToken;
663
+ }
664
+ },
665
+ _makeWatchRequestBody(id, address) {
666
+ const expiration =
667
+ Date.now() + WEBHOOK_SUBSCRIPTION_EXPIRATION_TIME_MILLISECONDS;
668
+ return {
669
+ id, // the component-specific channel ID, a UUID
670
+ type: "web_hook",
671
+ address, // the component-specific HTTP endpoint
672
+ expiration,
673
+ };
674
+ },
675
+ async watchDrive(id, address, pageToken, driveId) {
676
+ const drive = this.drive();
677
+ const requestBody = this._makeWatchRequestBody(id, address);
678
+ let watchRequest = {
679
+ pageToken,
680
+ requestBody,
681
+ };
682
+
683
+ // Google expects an entirely different object to be passed
684
+ // when you make a watch request for My Drive vs. a shared drive
685
+ // "My Drive" doesn't have a driveId, so if this method is called
686
+ // without a driveId, we make a watch request for My Drive
687
+ if (driveId) {
688
+ watchRequest = {
689
+ ...watchRequest,
690
+ driveId,
691
+ includeItemsFromAllDrives: true,
692
+ supportsAllDrives: true,
693
+ };
694
+ }
695
+
696
+ // When watching for changes to an entire account, we must pass a pageToken,
697
+ // which points to the moment in time we want to start watching for changes:
698
+ // https://developers.google.com/drive/api/v3/manage-changes
699
+ const {
700
+ expiration,
701
+ resourceId,
702
+ } = (
703
+ await drive.changes.watch(watchRequest)
704
+ ).data;
705
+ console.log(`Watch request for drive successful, expiry: ${expiration}`);
706
+ return {
707
+ expiration: parseInt(expiration),
708
+ resourceId,
709
+ };
710
+ },
711
+ async watchFile(id, address, fileId) {
712
+ const drive = this.drive();
713
+ const requestBody = this._makeWatchRequestBody(id, address);
714
+ const {
715
+ expiration,
716
+ resourceId,
717
+ } = (
718
+ await drive.files.watch({
719
+ fileId,
720
+ requestBody,
721
+ supportsAllDrives: true,
722
+ })
723
+ ).data;
724
+ console.log(
725
+ `Watch request for file ${fileId} successful, expiry: ${expiration}`,
726
+ );
727
+ return {
728
+ expiration: parseInt(expiration),
729
+ resourceId,
730
+ };
731
+ },
732
+ async stopNotifications(id, resourceId) {
733
+ // id = channelID
734
+ // See https://github.com/googleapis/google-api-nodejs-client/issues/627
735
+ const drive = this.drive();
736
+
737
+ // If for some reason the channel doesn't exist, this throws an error
738
+ // It's OK for this to fail in those cases, since we'll renew the channel
739
+ // immediately after trying to stop it if we still want notifications,
740
+ // so we squash the error, log it, and move on.
741
+ try {
742
+ await drive.channels.stop({
743
+ resource: {
744
+ id,
745
+ resourceId,
746
+ },
747
+ });
748
+ console.log(`Stopped push notifications on channel ${id}`);
749
+ } catch (err) {
750
+ console.error(
751
+ `Failed to stop channel ${id} for resource ${resourceId}: ${err}`,
752
+ );
753
+ }
754
+ },
755
+ /**
756
+ * Get a file in a drive
757
+ *
758
+ * @param {string} fileId - the ID value of the file to get
759
+ * @param {object} [params={}] - an object representing parameters used to
760
+ * get a file
761
+ * @param {string} [params.fields="*"] - the paths of the fields to include
762
+ * in the response
763
+ * @param {string} [params.alt] - if set to `media`, then the response
764
+ * includes the file contents in the response body
765
+ * @param {...*} [params.extraParams] - extra/optional parameters to be fed
766
+ * to the GDrive API call, as defined in [the API
767
+ * docs](https://bit.ly/3i5ctkS)
768
+ * @returns the file
769
+ */
770
+ async getFile(fileId, params = {}) {
771
+ const {
772
+ fields = "*",
773
+ alt,
774
+ ...extraParams
775
+ } = params;
776
+ const drive = this.drive();
777
+ return (
778
+ await drive.files.get({
779
+ fileId,
780
+ fields,
781
+ alt,
782
+ supportsAllDrives: true,
783
+ ...extraParams,
784
+ }, (alt === "media")
785
+ ? {
786
+ responseType: "stream",
787
+ }
788
+ : undefined)
789
+ ).data;
790
+ },
791
+ async getDrive(driveId) {
792
+ const drive = this.drive();
793
+ return (
794
+ await drive.drives.get({
795
+ driveId,
796
+ })
797
+ ).data;
798
+ },
799
+ async activateHook(channelID, url, drive) {
800
+ const startPageToken = await this.getPageToken(drive);
801
+ const {
802
+ expiration,
803
+ resourceId,
804
+ } = await this.watchDrive(
805
+ channelID,
806
+ url,
807
+ startPageToken,
808
+ drive,
809
+ );
810
+ return {
811
+ startPageToken,
812
+ expiration,
813
+ resourceId,
814
+ };
815
+ },
816
+ async activateFileHook(channelID, url, fileId) {
817
+ channelID = channelID || uuid();
818
+
819
+ const {
820
+ expiration,
821
+ resourceId,
822
+ } = await this.watchFile(
823
+ channelID,
824
+ url,
825
+ fileId,
826
+ );
827
+
828
+ return {
829
+ expiration,
830
+ resourceId,
831
+ channelID,
832
+ };
833
+ },
834
+ async deactivateHook(channelID, resourceId) {
835
+ if (!channelID) {
836
+ console.log(
837
+ "Channel not found, cannot stop notifications for non-existent channel",
838
+ );
839
+ return;
840
+ }
841
+
842
+ if (!resourceId) {
843
+ console.log(
844
+ "No resource ID found, cannot stop notifications for non-existent resource",
845
+ );
846
+ return;
847
+ }
848
+
849
+ await this.stopNotifications(channelID, resourceId);
850
+ },
851
+ async renewSubscription(drive, subscription, url, channelID, pageToken) {
852
+ const newChannelID = channelID || uuid();
853
+ const driveId = this.getDriveId(drive);
854
+ const newPageToken = pageToken || (await this.getPageToken(driveId));
855
+
856
+ const {
857
+ expiration,
858
+ resourceId,
859
+ } = await this.checkResubscription(
860
+ subscription,
861
+ newChannelID,
862
+ newPageToken,
863
+ url,
864
+ drive,
865
+ );
866
+
867
+ return {
868
+ newChannelID,
869
+ newPageToken,
870
+ expiration,
871
+ resourceId,
872
+ };
873
+ },
874
+ async checkResubscription(
875
+ subscription,
876
+ channelID,
877
+ pageToken,
878
+ endpoint,
879
+ drive,
880
+ ) {
881
+ const driveId = this.getDriveId(drive);
882
+ if (subscription && subscription.resourceId) {
883
+ console.log(
884
+ `Notifications for resource ${subscription.resourceId} are expiring at ${subscription.expiration}.
885
+ Stopping existing sub`,
886
+ );
887
+ await this.stopNotifications(channelID, subscription.resourceId);
888
+ }
889
+
890
+ const {
891
+ expiration,
892
+ resourceId,
893
+ } = await this.watchDrive(
894
+ channelID,
895
+ endpoint,
896
+ pageToken,
897
+ driveId,
898
+ );
899
+ return {
900
+ expiration,
901
+ resourceId,
902
+ };
903
+ },
904
+ async renewFileSubscription(subscription, url, channelID, fileId, nextRunTimestamp) {
905
+ if (nextRunTimestamp && subscription?.expiration < nextRunTimestamp) {
906
+ return subscription;
907
+ }
908
+
909
+ const newChannelID = channelID || uuid();
910
+
911
+ if (subscription?.resourceId) {
912
+ console.log(
913
+ `Notifications for resource ${subscription.resourceId} are expiring at ${subscription.expiration}. Renewing`,
914
+ );
915
+ await this.stopNotifications(
916
+ channelID,
917
+ subscription.resourceId,
918
+ );
919
+ }
920
+ const {
921
+ expiration,
922
+ resourceId,
923
+ } = await this.watchFile(
924
+ channelID,
925
+ url,
926
+ fileId,
927
+ );
928
+
929
+ return {
930
+ newChannelID,
931
+ expiration,
932
+ resourceId,
933
+ };
934
+ },
935
+ /**
936
+ * Get a filtered list of folders
937
+ *
938
+ * @param {object} [opts={}] - an object representing configuration options
939
+ * used to filter the folders that are listed
940
+ * @param {string} [opts.drive] - the ID value of a Google Drive, as
941
+ * provided by the `drive` prop definition of this app
942
+ * @param {string} [opts.name] - the name of the folder to find
943
+ * @param {string} [opts.parentId] - the ID of the parent folder of the
944
+ * folder to find, used to filter the listed folders
945
+ * @param {boolean} [opts.excludeTrashed=true] - `true` if folders in the
946
+ * trash should be excluded
947
+ * @returns the list of folders
948
+ */
949
+ async findFolder(opts = {}) {
950
+ const {
951
+ drive: driveProp,
952
+ name,
953
+ parentId,
954
+ excludeTrashed = true,
955
+ } = opts;
956
+ const drive = this.drive();
957
+ let q = `mimeType = '${GOOGLE_DRIVE_FOLDER_MIME_TYPE}'`;
958
+ if (name) {
959
+ q += ` and name = '${name}'`;
960
+ }
961
+ if (parentId) {
962
+ q += ` and '${parentId}' in parents`;
963
+ }
964
+ if (excludeTrashed) {
965
+ q += " and trashed != true";
966
+ }
967
+ const listOpts = getListFilesOpts(driveProp, {
968
+ q,
969
+ });
970
+ return (await drive.files.list(listOpts)).data.files;
971
+ },
972
+ /**
973
+ * Create a file in a drive
974
+ *
975
+ * @param {object} [opts={}] - an object representing configuration options
976
+ * used to create a file
977
+ * @param {stream.Readable} [opts.file] - a file stream to create the file
978
+ * with
979
+ * @param {string} [opts.mimeType] - the MIME type of the file to create
980
+ * @param {string} [opts.name] - the name of the file to create
981
+ * @param {string} [opts.parentId] - the ID value of the parent folder to
982
+ * create the file in
983
+ * @param {string} [opts.driveId] - the ID value of a Google Drive to create
984
+ * the file in if `parentId` is undefined
985
+ * @param {string} [opts.fields] - the paths of the fields to include in the
986
+ * response
987
+ * @param {string} [opts.supportsAllDrives=true] - whether the requesting
988
+ * application supports both My Drives and shared drives
989
+ * @param {object} [opts.requestBody] - extra/optional properties to be used
990
+ * in the request body of the GDrive API, as defined in [the API
991
+ * docs](https://bit.ly/3kuvbnq)
992
+ * @param {...*} [opts.extraParams] - extra/optional parameters to be fed to
993
+ * the GDrive API call, as defined in [the API docs](https://bit.ly/2VY0MVg)
994
+ * @returns the created file
995
+ */
996
+ async createFile(opts = {}) {
997
+ const {
998
+ file,
999
+ mimeType,
1000
+ name,
1001
+ parentId,
1002
+ driveId,
1003
+ fields,
1004
+ supportsAllDrives = true,
1005
+ requestBody,
1006
+ ...extraParams
1007
+ } = opts;
1008
+ const drive = this.drive();
1009
+ const parent = parentId ?? driveId;
1010
+ return (
1011
+ await drive.files.create({
1012
+ fields,
1013
+ supportsAllDrives,
1014
+ media: file
1015
+ ? {
1016
+ mimeType,
1017
+ body: file,
1018
+ }
1019
+ : undefined,
1020
+ requestBody: {
1021
+ name,
1022
+ mimeType,
1023
+ parents: parent
1024
+ ? [
1025
+ parent,
1026
+ ]
1027
+ : undefined,
1028
+ ...extraParams.resource,
1029
+ ...requestBody,
1030
+ },
1031
+ ...extraParams,
1032
+ })
1033
+ ).data;
1034
+ },
1035
+ /**
1036
+ * Create a folder in a drive
1037
+ *
1038
+ * @param {object} [opts={}] - an object representing configuration options
1039
+ * used to create a folder
1040
+ * @param {string} [opts.name] - the name of the folder to create
1041
+ * @param {string} [opts.parentId] - the ID value of the parent folder to
1042
+ * create the folder in
1043
+ * @param {string} [opts.fields="*"] - the paths of the fields to include in
1044
+ * the response
1045
+ * @param {...*} [opts.extraParams] - extra/optional parameters to be fed to
1046
+ * the GDrive API call, as defined in [the API docs](https://bit.ly/2VY0MVg)
1047
+ * @returns the created folder
1048
+ */
1049
+ async createFolder(opts = {}) {
1050
+ const {
1051
+ name,
1052
+ parentId,
1053
+ fields = "*",
1054
+ ...extraParams
1055
+ } = opts;
1056
+ return await this.createFile({
1057
+ name,
1058
+ parentId,
1059
+ fields,
1060
+ mimeType: `${GOOGLE_DRIVE_FOLDER_MIME_TYPE}`,
1061
+ ...extraParams,
1062
+ });
1063
+ },
1064
+ /**
1065
+ * Update a file's media content
1066
+ *
1067
+ * @param {string} fileId - the ID value of the file to update
1068
+ * @param {stream.Readable} [fileStream] - a file stream used to update the
1069
+ * content of the file
1070
+ * @param {object} [opts={}] - an object representing configuration options
1071
+ * used to update a file
1072
+ * @param {string} [opts.mimeType] - the MIME type of the file
1073
+ * used to update the file content
1074
+ * @param {string} [opts.supportsAllDrives=true] - whether the requesting
1075
+ * application supports both My Drives and shared drives
1076
+ * @param {...*} [opts.extraParams] - extra/optional parameters to be fed to
1077
+ * the GDrive API call, as defined in [the API docs](https://bit.ly/3lNg9Zw)
1078
+ * @returns the updated file
1079
+ */
1080
+ async updateFileMedia(fileId, fileStream, opts = {}) {
1081
+ const {
1082
+ mimeType,
1083
+ supportsAllDrives = true,
1084
+ ...extraParams
1085
+ } = opts;
1086
+ const drive = this.drive();
1087
+ return (
1088
+ await drive.files.update({
1089
+ fileId,
1090
+ supportsAllDrives,
1091
+ media: {
1092
+ mimeType,
1093
+ body: fileStream,
1094
+ },
1095
+ ...extraParams,
1096
+ })
1097
+ ).data;
1098
+ },
1099
+ /**
1100
+ * Update a file's metadata
1101
+ *
1102
+ * @param {string} fileId - the ID value of the file to update
1103
+ * @param {object} [opts={}] - an object representing configuration options
1104
+ * used to update a file
1105
+ * @param {string} [opts.name] - the updated name of the file
1106
+ * @param {string} [opts.mimeType] - the updated MIME type of the file
1107
+ * @param {string} [opts.fields] - the paths of the fields to include in
1108
+ * the response
1109
+ * @param {string} [opts.removeParents] - a comma-separated list of parent
1110
+ * folder IDs to add to the file's parents
1111
+ * @param {string} [opts.addParents] - a comma-separated list of parent
1112
+ * folder IDs to add to the file's parents
1113
+ * @param {string} [opts.supportsAllDrives=true] - whether the requesting
1114
+ * application supports both My Drives and shared drives
1115
+ * @param {object} [opts.requestBody] - extra/optional properties to be used
1116
+ * in the request body of the GDrive API, as defined in
1117
+ * [the API docs](https://bit.ly/3nTMi4n)
1118
+ * @param {...*} [opts.extraParams] - extra/optional parameters to be fed to
1119
+ * the GDrive API call, as defined in [the API docs](https://bit.ly/3lNg9Zw)
1120
+ * @returns the updated file
1121
+ */
1122
+ async updateFile(fileId, opts = {}) {
1123
+ const {
1124
+ name,
1125
+ mimeType,
1126
+ fields,
1127
+ removeParents,
1128
+ addParents,
1129
+ supportsAllDrives = true,
1130
+ requestBody,
1131
+ ...extraParams
1132
+ } = opts;
1133
+ const drive = this.drive();
1134
+ return (
1135
+ await drive.files.update({
1136
+ fileId,
1137
+ removeParents,
1138
+ addParents,
1139
+ fields,
1140
+ supportsAllDrives,
1141
+ requestBody: {
1142
+ name,
1143
+ mimeType,
1144
+ ...requestBody,
1145
+ },
1146
+ ...extraParams,
1147
+ })
1148
+ ).data;
1149
+ },
1150
+ /**
1151
+ * Copy a file
1152
+ *
1153
+ * @param {string} fileId - the ID value of the file to copy
1154
+ * @param {object} [opts={}] - an object representing configuration options
1155
+ * used to copy a file
1156
+ * @param {string} [opts.fields="*"] - the paths of the fields to include in
1157
+ * the response
1158
+ * @param {string} [opts.supportsAllDrives=true] - whether the requesting
1159
+ * application supports both My Drives and shared drives
1160
+ * @param {...*} [opts.extraParams] - extra/optional parameters to be fed to
1161
+ * the GDrive API call, as defined in [the API docs](https://bit.ly/3kq5eFO)
1162
+ * @returns the copy of the file
1163
+ */
1164
+ async copyFile(fileId, opts = {}) {
1165
+ const {
1166
+ fields = "*",
1167
+ supportsAllDrives = true,
1168
+ ...extraParams
1169
+ } = opts;
1170
+ const drive = this.drive();
1171
+ return (
1172
+ await drive.files.copy({
1173
+ fileId,
1174
+ fields,
1175
+ supportsAllDrives,
1176
+ ...extraParams,
1177
+ })
1178
+ ).data;
1179
+ },
1180
+ /**
1181
+ * Delete a file
1182
+ *
1183
+ * @param {string} fileId - the ID value of the file to delete
1184
+ * @param {object} [params={}] - an object representing parameters used to
1185
+ * delete a file
1186
+ * @param {string} [params.supportsAllDrives=true] - whether the requesting
1187
+ * application supports both My Drives and shared drives
1188
+ * @param {...*} [params.extraParams] - extra/optional parameters to be fed
1189
+ * to the GDrive API call, as defined in [the API
1190
+ * docs](https://bit.ly/3MjRkB7)
1191
+ * @returns {void}
1192
+ */
1193
+ async deleteFile(fileId, params = {}) {
1194
+ const {
1195
+ supportsAllDrives = true,
1196
+ ...extraParams
1197
+ } = params;
1198
+ const drive = this.drive();
1199
+ return (
1200
+ await drive.files.delete({
1201
+ fileId,
1202
+ supportsAllDrives,
1203
+ ...extraParams,
1204
+ })
1205
+ ).data;
1206
+ },
1207
+ /**
1208
+ * Download a Google Workspace document using the
1209
+ * [files.export](https://bit.ly/2Zkrxo8) method
1210
+ *
1211
+ * @param {string} fileId - the ID value of the file to download
1212
+ * @param {object} [params={}] - an object representing parameters used to
1213
+ * download a Workspace file
1214
+ * @param {string} [params.mimeType] - the MIME type to which to export the
1215
+ * document
1216
+ * @param {...*} [params.extraParams] - extra/optional parameters to be fed to
1217
+ * the GDrive API call, as defined in [the API docs](https://bit.ly/3o6rRRF)
1218
+ * @returns the file download
1219
+ */
1220
+ async downloadWorkspaceFile(fileId, params = {}) {
1221
+ const {
1222
+ mimeType,
1223
+ ...extraParams
1224
+ } = params;
1225
+ const drive = this.drive();
1226
+ return (
1227
+ await drive.files.export({
1228
+ fileId,
1229
+ mimeType,
1230
+ ...extraParams,
1231
+ }, {
1232
+ responseType: "stream",
1233
+ })
1234
+ ).data;
1235
+ },
1236
+ /**
1237
+ * Create a permission for a file
1238
+ *
1239
+ * @param {string} fileId - the ID value of the file for which to create a
1240
+ * Permission
1241
+ * @param {object} [opts={}] - an object representing configuration options
1242
+ * used to create a permission
1243
+ * @param {string} [opts.role="reader"] - the role granted by this
1244
+ * permission. Currently, one of `owner`,`organizer`,`fileOrganizer`,
1245
+ * `writer`,`commenter`, `reader`.
1246
+ * @param {string} [opts.type] - the type of the grantee. Valid values are:
1247
+ * `user`,`group`,`domain`,`anyone`.
1248
+ * @param {string} [opts.domain] - the domain to which this permission
1249
+ * refers
1250
+ * @param {string} [opts.emailAddress] - the email address of the user or
1251
+ * group to which this permission refers
1252
+ * @param {string} [opts.supportsAllDrives=true] - whether the requesting
1253
+ * application supports both My Drives and shared drives
1254
+ * @returns the created Permission
1255
+ */
1256
+ async createPermission(fileId, opts = {}) {
1257
+ const {
1258
+ role = "reader",
1259
+ type,
1260
+ domain,
1261
+ emailAddress,
1262
+ supportsAllDrives = true,
1263
+ } = opts;
1264
+ const drive = this.drive();
1265
+ return (
1266
+ await drive.permissions.create({
1267
+ fileId,
1268
+ supportsAllDrives,
1269
+ requestBody: omitEmptyStringValues({
1270
+ role,
1271
+ type,
1272
+ domain,
1273
+ emailAddress,
1274
+ }),
1275
+ })
1276
+ ).data;
1277
+ },
1278
+ /**
1279
+ * Get a shared drive
1280
+ *
1281
+ * @param {string} driveId - the ID value of the drive
1282
+ * @param {object} [opts={}] - an object representing configuration options
1283
+ * used to get a shared drive
1284
+ * @param {boolean} [opts.useDomainAccess] - if the request should issued a
1285
+ * domain administrator, granted if the requester an administrator of the
1286
+ * domain to which the shared drive belongs
1287
+ * @returns the shared drive
1288
+ */
1289
+ async getSharedDrive(driveId, opts = {}) {
1290
+ const { useDomainAdminAccess } = opts;
1291
+ const drive = this.drive();
1292
+ return (
1293
+ await drive.drives.get({
1294
+ driveId,
1295
+ useDomainAdminAccess,
1296
+ })
1297
+ ).data;
1298
+ },
1299
+ /**
1300
+ * Search for drives according to the list parameters
1301
+ *
1302
+ * @param {object} [opts={}] - an object representing configuration options
1303
+ * used to search for drives
1304
+ * @param {string} [opts.q] - query string for searching shared drives. See
1305
+ * the ["Search for shared drives"](https://bit.ly/2XJ1oik) guide for
1306
+ * supported syntax.
1307
+ * @param {boolean} [opts.useDomainAccess] - if the request should issued a
1308
+ * domain administrator, granted if the requester an administrator of the
1309
+ * domain to which the shared drive belongs
1310
+ * @param {...*} [opts.extraParams] - extra/optional parameters to be fed to
1311
+ * the GDrive API call, as defined in [the API docs](https://bit.ly/3CCf4e3)
1312
+ * the response
1313
+ * @returns a list of drives
1314
+ */
1315
+ async searchDrives(opts = {}) {
1316
+ const {
1317
+ q,
1318
+ useDomainAdminAccess,
1319
+ ...extraParams
1320
+ } = opts;
1321
+ const drive = this.drive();
1322
+ return (
1323
+ await drive.drives.list({
1324
+ q,
1325
+ useDomainAdminAccess,
1326
+ ...extraParams,
1327
+ })
1328
+ ).data;
1329
+ },
1330
+ /**
1331
+ * Create a drive
1332
+ *
1333
+ * @param {object} [opts={}] - an object representing configuration options
1334
+ * used to create a drive
1335
+ * @param {string} [opts.name] - the name of the drive to create
1336
+ * @param {object} [opts.requestBody] - extra/optional properties to be used
1337
+ * in the request body of the GDrive API, as defined in
1338
+ * [the API docs](https://bit.ly/2ZiyIgJ)
1339
+ * @returns the created drive
1340
+ */
1341
+ async createDrive(opts = {}) {
1342
+ const {
1343
+ name,
1344
+ requestBody,
1345
+ } = opts;
1346
+ const drive = this.drive();
1347
+ return (
1348
+ await drive.drives.create({
1349
+ requestId: uuid(),
1350
+ requestBody: {
1351
+ name,
1352
+ ...requestBody,
1353
+ },
1354
+ })
1355
+ ).data;
1356
+ },
1357
+ /**
1358
+ * Update a shared drive
1359
+ *
1360
+ * @param {string} driveId - the ID value of the drive
1361
+ * @param {object} [opts={}] - an object representing configuration options
1362
+ * used to update a shared drive
1363
+ * @param {boolean} [opts.useDomainAccess] - if the request should issued a
1364
+ * domain administrator, granted if the requester an administrator of the
1365
+ * domain to which the shared drive belongs
1366
+ * @param {object} [opts.requestBody] - extra/optional properties to be used
1367
+ * in the request body of the GDrive API, as defined in
1368
+ * [the API docs](https://bit.ly/3AxJP3c)
1369
+ * @returns the updated shared drive
1370
+ */
1371
+ async updateSharedDrive(driveId, opts = {}) {
1372
+ const {
1373
+ useDomainAdminAccess,
1374
+ requestBody,
1375
+ } = opts;
1376
+ const drive = this.drive();
1377
+ return (
1378
+ await drive.drives.update({
1379
+ driveId,
1380
+ useDomainAdminAccess,
1381
+ requestBody: {
1382
+ ...requestBody,
1383
+ },
1384
+ })
1385
+ ).data;
1386
+ },
1387
+ /**
1388
+ * Delete a shared drive
1389
+ *
1390
+ * @param {string} driveId - the ID value of the drive to delete
1391
+ * @returns {void}
1392
+ */
1393
+ async deleteSharedDrive(driveId) {
1394
+ const drive = this.drive();
1395
+ return (
1396
+ await drive.drives.delete({
1397
+ driveId,
1398
+ })
1399
+ ).data;
1400
+ },
1401
+ /**
1402
+ * Gets information about the user, the user's Drive, and system capabilities. It is a wrapper
1403
+ * around the [the `about.get`
1404
+ * API]{@link https://developers.google.com/drive/api/v3/reference/about/get}.
1405
+ *
1406
+ * @param {string} [fields="*"] - the paths of the fields to include in the response
1407
+ * @returns an About resource
1408
+ */
1409
+ async getAbout(fields = "*") {
1410
+ const drive = this.drive();
1411
+ return (
1412
+ await drive.about.get({
1413
+ fields,
1414
+ })
1415
+ ).data;
1416
+ },
1417
+ /**
1418
+ * Get a list of all supported export formats supported by the system for this user
1419
+ *
1420
+ * @see
1421
+ * {@link https://bit.ly/3HRbUqd Google Workspace documents and corresponding export MIME types}
1422
+ *
1423
+ * @returns a list of supported export formats for each Google Workspace format
1424
+ */
1425
+ async getExportFormats() {
1426
+ return (await this.getAbout("exportFormats")).exportFormats;
1427
+ },
1428
+ },
1429
+ };