@pipedream/google_drive 3.0.0 → 4.0.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.
- package/actions/add-comment/add-comment.mjs +1 -1
- package/actions/add-file-sharing-preference/add-file-sharing-preference.mjs +1 -1
- package/actions/copy-file/copy-file.mjs +1 -1
- package/actions/create-file-from-template/create-file-from-template.mjs +1 -1
- package/actions/create-file-from-text/create-file-from-text.mjs +1 -1
- package/actions/create-folder/create-folder.mjs +1 -1
- package/actions/create-shared-drive/create-shared-drive.mjs +1 -1
- package/actions/create-text-file/create-text-file.mjs +1 -1
- package/actions/delete-comment/delete-comment.mjs +1 -1
- package/actions/delete-file/delete-file.mjs +1 -1
- package/actions/delete-reply/delete-reply.mjs +1 -1
- package/actions/delete-shared-drive/delete-shared-drive.mjs +1 -1
- package/actions/download-file/download-file.mjs +18 -10
- package/actions/find-file/find-file.mjs +1 -1
- package/actions/find-folder/find-folder.mjs +1 -1
- package/actions/find-forms/find-forms.mjs +1 -1
- package/actions/find-spreadsheets/find-spreadsheets.mjs +1 -1
- package/actions/get-comment/get-comment.mjs +1 -1
- package/actions/get-current-user/get-current-user.mjs +1 -1
- package/actions/get-file/get-file.mjs +1 -1
- package/actions/get-file-by-id/get-file-by-id.mjs +1 -1
- package/actions/get-folder-id-for-path/get-folder-id-for-path.mjs +1 -1
- package/actions/get-reply/get-reply.mjs +1 -1
- package/actions/get-shared-drive/get-shared-drive.mjs +1 -1
- package/actions/get-user-details/get-user-details.mjs +1 -1
- package/actions/is-folder-ancestor/is-folder-ancestor.mjs +1 -1
- package/actions/list-access-proposals/list-access-proposals.mjs +1 -1
- package/actions/list-comments/list-comments.mjs +1 -1
- package/actions/list-files/list-files.mjs +1 -1
- package/actions/list-mime-type-options/list-mime-type-options.mjs +1 -1
- package/actions/list-permissions/list-permissions.mjs +1 -1
- package/actions/list-replies/list-replies.mjs +1 -1
- package/actions/list-shared-drives/list-shared-drives.mjs +1 -1
- package/actions/list-theme-id-options/list-theme-id-options.mjs +1 -1
- package/actions/move-file/move-file.mjs +1 -1
- package/actions/move-file-to-trash/move-file-to-trash.mjs +1 -1
- package/actions/remove-file-sharing-permission/remove-file-sharing-permission.mjs +1 -1
- package/actions/remove-sharing/remove-sharing.mjs +1 -1
- package/actions/reply-to-comment/reply-to-comment.mjs +1 -1
- package/actions/resolve-access-proposal/resolve-access-proposal.mjs +1 -1
- package/actions/resolve-comment/resolve-comment.mjs +1 -1
- package/actions/search-files/search-files.mjs +4 -4
- package/actions/search-shared-drives/search-shared-drives.mjs +1 -1
- package/actions/share-file/share-file.mjs +1 -1
- package/actions/trash-file/trash-file.mjs +1 -1
- package/actions/update-comment/update-comment.mjs +1 -1
- package/actions/update-file/update-file.mjs +1 -1
- package/actions/update-reply/update-reply.mjs +1 -1
- package/actions/update-shared-drive/update-shared-drive.mjs +1 -1
- package/actions/upload-file/upload-file.mjs +1 -1
- package/common/constants.mjs +29 -0
- package/common/utils.mjs +61 -7
- package/google_drive.app.mjs +21 -6
- package/package.json +1 -1
- package/sources/changes-to-files-in-drive/changes-to-files-in-drive.mjs +1 -1
- package/sources/changes-to-specific-files/changes-to-specific-files.mjs +1 -1
- package/sources/changes-to-specific-files-shared-drive/changes-to-specific-files-shared-drive.mjs +1 -1
- package/sources/common-dedupe-changes.mjs +23 -12
- package/sources/common-webhook.mjs +12 -1
- package/sources/new-access-proposal/new-access-proposal.mjs +1 -1
- package/sources/new-files-instant/new-files-instant.mjs +1 -1
- package/sources/new-files-instant-polling/new-files-instant-polling.mjs +1 -1
- package/sources/new-files-shared-drive/new-files-shared-drive.mjs +1 -1
- package/sources/new-or-modified-comments/new-or-modified-comments.mjs +1 -1
- package/sources/new-or-modified-comments-polling/new-or-modified-comments-polling.mjs +1 -1
- package/sources/new-or-modified-files/new-or-modified-files.mjs +59 -42
- package/sources/new-or-modified-files/test-event.mjs +30 -1
- package/sources/new-or-modified-files-polling/new-or-modified-files-polling.mjs +1 -1
- package/sources/new-or-modified-folders/new-or-modified-folders.mjs +1 -1
- package/sources/new-or-modified-folders-polling/new-or-modified-folders-polling.mjs +1 -1
- package/sources/new-shared-drive/new-shared-drive.mjs +1 -1
- package/sources/new-spreadsheet/new-spreadsheet.mjs +1 -1
- package/sources/new-spreadsheet-polling/new-spreadsheet-polling.mjs +1 -1
package/common/constants.mjs
CHANGED
|
@@ -257,6 +257,30 @@ const FILES_MAX_PAGE_SIZE = 1000;
|
|
|
257
257
|
*/
|
|
258
258
|
const DEFAULT_SEARCH_FILES_LIMIT = 100;
|
|
259
259
|
|
|
260
|
+
const RETRYABLE_STATUS_CODES = [
|
|
261
|
+
422,
|
|
262
|
+
429,
|
|
263
|
+
500,
|
|
264
|
+
502,
|
|
265
|
+
503,
|
|
266
|
+
504,
|
|
267
|
+
];
|
|
268
|
+
|
|
269
|
+
const RATE_LIMIT_ERROR_REASONS = [
|
|
270
|
+
"rateLimitExceeded",
|
|
271
|
+
"userRateLimitExceeded",
|
|
272
|
+
];
|
|
273
|
+
|
|
274
|
+
const CHANGED_FILE_FIELDS = "kind,id,name,mimeType,parents,createdTime,modifiedTime,trashed,version,size,md5Checksum,webViewLink,lastModifyingUser";
|
|
275
|
+
|
|
276
|
+
/** Google Workspace types that `stashFile` can export to PDF. */
|
|
277
|
+
const PDF_EXPORTABLE_MIME_TYPES = [
|
|
278
|
+
"application/vnd.google-apps.document",
|
|
279
|
+
"application/vnd.google-apps.spreadsheet",
|
|
280
|
+
"application/vnd.google-apps.presentation",
|
|
281
|
+
"application/vnd.google-apps.drawing",
|
|
282
|
+
];
|
|
283
|
+
|
|
260
284
|
export {
|
|
261
285
|
GOOGLE_DRIVE_NOTIFICATION_SYNC,
|
|
262
286
|
GOOGLE_DRIVE_NOTIFICATION_ADD,
|
|
@@ -300,4 +324,9 @@ export {
|
|
|
300
324
|
// Files
|
|
301
325
|
FILES_MAX_PAGE_SIZE,
|
|
302
326
|
DEFAULT_SEARCH_FILES_LIMIT,
|
|
327
|
+
// Webhooks
|
|
328
|
+
RETRYABLE_STATUS_CODES,
|
|
329
|
+
RATE_LIMIT_ERROR_REASONS,
|
|
330
|
+
CHANGED_FILE_FIELDS,
|
|
331
|
+
PDF_EXPORTABLE_MIME_TYPES,
|
|
303
332
|
};
|
package/common/utils.mjs
CHANGED
|
@@ -62,15 +62,14 @@ function getListFilesOpts(drive, baseOpts = {}) {
|
|
|
62
62
|
|
|
63
63
|
if (drive && !isMyDrive(drive)) {
|
|
64
64
|
return {
|
|
65
|
-
// Google's `files.list` API requires `includeItemsFromAllDrives: true`
|
|
66
|
-
// whenever `driveId` is set or `corpora` is `drive`/`allDrives`.
|
|
67
|
-
// Placed before the spread so callers (e.g. list-files's
|
|
68
|
-
// `limitToMyDrive` toggle) can still override.
|
|
69
|
-
includeItemsFromAllDrives: true,
|
|
70
65
|
...rest,
|
|
71
66
|
corpora: "drive",
|
|
72
67
|
driveId: getDriveId(drive),
|
|
73
68
|
supportsAllDrives: true,
|
|
69
|
+
// Google's `files.list` API requires `includeItemsFromAllDrives: true`
|
|
70
|
+
// whenever `driveId` is set or `corpora` is `drive`/`allDrives`.
|
|
71
|
+
// Placed AFTER the spread so callers cannot downgrade this required value.
|
|
72
|
+
includeItemsFromAllDrives: true,
|
|
74
73
|
};
|
|
75
74
|
}
|
|
76
75
|
|
|
@@ -81,12 +80,15 @@ function getListFilesOpts(drive, baseOpts = {}) {
|
|
|
81
80
|
};
|
|
82
81
|
}
|
|
83
82
|
|
|
84
|
-
// No specific drive selected
|
|
83
|
+
// No specific drive selected - search across all drives by default.
|
|
85
84
|
return {
|
|
86
|
-
includeItemsFromAllDrives: true,
|
|
87
85
|
...rest,
|
|
88
86
|
corpora: "allDrives",
|
|
89
87
|
supportsAllDrives: true,
|
|
88
|
+
// Google's `files.list` API requires `includeItemsFromAllDrives: true`
|
|
89
|
+
// when `corpora` is `allDrives`. Placed AFTER the spread so callers
|
|
90
|
+
// cannot downgrade this required value.
|
|
91
|
+
includeItemsFromAllDrives: true,
|
|
90
92
|
};
|
|
91
93
|
}
|
|
92
94
|
|
|
@@ -297,6 +299,56 @@ function parseObjectEntries(value = {}) {
|
|
|
297
299
|
);
|
|
298
300
|
}
|
|
299
301
|
|
|
302
|
+
/**
|
|
303
|
+
* Reserves a unique path among a set of paths already used in the current run,
|
|
304
|
+
* appending a numeric suffix (before the extension, if any) when `candidatePath`
|
|
305
|
+
* collides with one already reserved. Useful when generating file paths from
|
|
306
|
+
* user-controlled or duplicate-prone names (e.g. two Drive files sharing a
|
|
307
|
+
* name), so they don't silently overwrite one another on disk.
|
|
308
|
+
*
|
|
309
|
+
* @param {String} candidatePath the path to reserve
|
|
310
|
+
* @param {Set<String>} usedPaths paths already reserved in the current run;
|
|
311
|
+
* mutated in place to add whichever path is returned
|
|
312
|
+
* @returns `candidatePath` unchanged if it hasn't been used yet, otherwise a
|
|
313
|
+
* variant with a `" (n)"` suffix inserted before the extension
|
|
314
|
+
*/
|
|
315
|
+
function reserveUniqueFilePath(candidatePath, usedPaths) {
|
|
316
|
+
if (!usedPaths.has(candidatePath)) {
|
|
317
|
+
usedPaths.add(candidatePath);
|
|
318
|
+
return candidatePath;
|
|
319
|
+
}
|
|
320
|
+
const lastDot = candidatePath.lastIndexOf(".");
|
|
321
|
+
const lastSlash = candidatePath.lastIndexOf("/");
|
|
322
|
+
const hasExt = lastDot > lastSlash;
|
|
323
|
+
const base = hasExt
|
|
324
|
+
? candidatePath.slice(0, lastDot)
|
|
325
|
+
: candidatePath;
|
|
326
|
+
const ext = hasExt
|
|
327
|
+
? candidatePath.slice(lastDot)
|
|
328
|
+
: "";
|
|
329
|
+
let suffix = 2;
|
|
330
|
+
let uniquePath;
|
|
331
|
+
do {
|
|
332
|
+
uniquePath = `${base} (${suffix})${ext}`;
|
|
333
|
+
suffix += 1;
|
|
334
|
+
} while (usedPaths.has(uniquePath));
|
|
335
|
+
usedPaths.add(uniquePath);
|
|
336
|
+
return uniquePath;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Drive allows "/" and arbitrary length in names; neither is valid in a single path segment.
|
|
340
|
+
function sanitizeFileName(name) {
|
|
341
|
+
const safe = Buffer.from(name.replace(/[/\\\0]/g, "_"))
|
|
342
|
+
.subarray(0, 200)
|
|
343
|
+
.toString()
|
|
344
|
+
// Drop a multi-byte character split by the byte cut.
|
|
345
|
+
.replace(/�+$/, "");
|
|
346
|
+
if (!safe || safe === "." || safe === "..") {
|
|
347
|
+
return "_";
|
|
348
|
+
}
|
|
349
|
+
return safe;
|
|
350
|
+
}
|
|
351
|
+
|
|
300
352
|
async function stashFile(item, googleDrive, dir) {
|
|
301
353
|
const fileMetadata = await googleDrive.getFile(item.id, {
|
|
302
354
|
fields: "name,mimeType",
|
|
@@ -396,6 +448,8 @@ export {
|
|
|
396
448
|
omitEmptyStringValues,
|
|
397
449
|
parseObjectEntries,
|
|
398
450
|
parseRfc3339,
|
|
451
|
+
reserveUniqueFilePath,
|
|
452
|
+
sanitizeFileName,
|
|
399
453
|
stashFile,
|
|
400
454
|
streamToBuffer,
|
|
401
455
|
toSingleLineString,
|
package/google_drive.app.mjs
CHANGED
|
@@ -13,6 +13,8 @@ import {
|
|
|
13
13
|
GOOGLE_DRIVE_UPDATE_TYPES,
|
|
14
14
|
GOOGLE_DRIVE_UPLOAD_TYPE_OPTIONS,
|
|
15
15
|
MY_DRIVE_VALUE,
|
|
16
|
+
RATE_LIMIT_ERROR_REASONS,
|
|
17
|
+
RETRYABLE_STATUS_CODES,
|
|
16
18
|
WEBHOOK_SUBSCRIPTION_EXPIRATION_TIME_MILLISECONDS,
|
|
17
19
|
} from "./common/constants.mjs";
|
|
18
20
|
|
|
@@ -445,15 +447,20 @@ export default {
|
|
|
445
447
|
* returned
|
|
446
448
|
* @param {number} [pageSize=1000] - the maximum number of changes to return
|
|
447
449
|
* per page
|
|
450
|
+
* @param {string} [fileFields] - the file fields to return for each change,
|
|
451
|
+
* e.g. `id,name,parents`. Defaults to the API's minimal file fields
|
|
448
452
|
* @yields
|
|
449
453
|
* @type {ChangesPage}
|
|
450
454
|
*/
|
|
451
|
-
async *listChanges(pageToken, driveId, pageSize = 1000) {
|
|
455
|
+
async *listChanges(pageToken, driveId, pageSize = 1000, fileFields) {
|
|
452
456
|
const drive = this.drive();
|
|
453
457
|
let changeRequest = {
|
|
454
458
|
pageToken,
|
|
455
459
|
pageSize,
|
|
456
460
|
};
|
|
461
|
+
if (fileFields) {
|
|
462
|
+
changeRequest.fields = `nextPageToken,newStartPageToken,changes(file(${fileFields}))`;
|
|
463
|
+
}
|
|
457
464
|
|
|
458
465
|
// As with many of the methods for Google Drive, we must
|
|
459
466
|
// pass a request of a different shape when we're requesting
|
|
@@ -468,7 +475,9 @@ export default {
|
|
|
468
475
|
}
|
|
469
476
|
|
|
470
477
|
while (true) {
|
|
471
|
-
const { data } = await
|
|
478
|
+
const { data } = await this.retryWithExponentialBackoff(
|
|
479
|
+
() => drive.changes.list(changeRequest),
|
|
480
|
+
);
|
|
472
481
|
const {
|
|
473
482
|
changes = [],
|
|
474
483
|
newStartPageToken,
|
|
@@ -1693,6 +1702,14 @@ export default {
|
|
|
1693
1702
|
const drive = this.drive();
|
|
1694
1703
|
return (await drive.accessproposals.resolve(opts)).data;
|
|
1695
1704
|
},
|
|
1705
|
+
isRetryableError(error, statusCode) {
|
|
1706
|
+
if (RETRYABLE_STATUS_CODES.includes(statusCode)) {
|
|
1707
|
+
return true;
|
|
1708
|
+
}
|
|
1709
|
+
const errors = error.errors ?? error.response?.data?.error?.errors ?? [];
|
|
1710
|
+
return statusCode === 403
|
|
1711
|
+
&& errors.some(({ reason }) => RATE_LIMIT_ERROR_REASONS.includes(reason));
|
|
1712
|
+
},
|
|
1696
1713
|
retryWithExponentialBackoff(func, maxAttempts = 3, baseDelayS = 2) {
|
|
1697
1714
|
let attempt = 0;
|
|
1698
1715
|
|
|
@@ -1700,15 +1717,13 @@ export default {
|
|
|
1700
1717
|
try {
|
|
1701
1718
|
return await func();
|
|
1702
1719
|
} catch (error) {
|
|
1703
|
-
// retry for error status 422
|
|
1704
1720
|
const statusCode = error.status || error.response?.status;
|
|
1705
|
-
if (attempt >= maxAttempts || statusCode
|
|
1721
|
+
if (attempt >= maxAttempts || !this.isRetryableError(error, statusCode)) {
|
|
1706
1722
|
throw error;
|
|
1707
1723
|
}
|
|
1708
1724
|
|
|
1709
|
-
// display error message for 422 status
|
|
1710
1725
|
const errorMessage = error.message || error.response?.data?.message || error.response?.statusText || "Unknown error";
|
|
1711
|
-
console.log(`Received
|
|
1726
|
+
console.log(`Received ${statusCode} error: ${errorMessage}. Retrying attempt ${attempt + 1}/${maxAttempts}...`);
|
|
1712
1727
|
|
|
1713
1728
|
const delayMs = Math.pow(baseDelayS, attempt) * 1000;
|
|
1714
1729
|
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
package/package.json
CHANGED
|
@@ -14,7 +14,7 @@ export default {
|
|
|
14
14
|
key: "google_drive-changes-to-files-in-drive",
|
|
15
15
|
name: "Changes to Files in Drive",
|
|
16
16
|
description: "Emit new event when a change is made to one of the specified files. [See the documentation](https://developers.google.com/drive/api/v3/reference/changes/watch)",
|
|
17
|
-
version: "0.0.
|
|
17
|
+
version: "0.0.16",
|
|
18
18
|
type: "source",
|
|
19
19
|
dedupe: "unique",
|
|
20
20
|
props: {
|
|
@@ -16,7 +16,7 @@ export default {
|
|
|
16
16
|
key: "google_drive-changes-to-specific-files",
|
|
17
17
|
name: "Changes to Specific Files",
|
|
18
18
|
description: "Watches for changes to specific files, emitting an event when a change is made to one of those files. To watch for changes to [shared drive](https://support.google.com/a/users/answer/9310351) files, use the **Changes to Specific Files (Shared Drive)** source instead.",
|
|
19
|
-
version: "0.3.
|
|
19
|
+
version: "0.3.14",
|
|
20
20
|
type: "source",
|
|
21
21
|
// Dedupe events based on the "x-goog-message-number" header for the target channel:
|
|
22
22
|
// https://developers.google.com/drive/api/v3/push#making-watch-requests
|
package/sources/changes-to-specific-files-shared-drive/changes-to-specific-files-shared-drive.mjs
CHANGED
|
@@ -29,7 +29,7 @@ export default {
|
|
|
29
29
|
key: "google_drive-changes-to-specific-files-shared-drive",
|
|
30
30
|
name: "Changes to Specific Files (Shared Drive)",
|
|
31
31
|
description: "Watches for changes to specific files in a shared drive, emitting an event when a change is made to one of those files",
|
|
32
|
-
version: "0.3.
|
|
32
|
+
version: "0.3.16",
|
|
33
33
|
type: "source",
|
|
34
34
|
// Dedupe events based on the "x-goog-message-number" header for the target channel:
|
|
35
35
|
// https://developers.google.com/drive/api/v3/push#making-watch-requests
|
|
@@ -24,29 +24,40 @@ You can change or disable this minimum interval using the prop \`Minimum Interva
|
|
|
24
24
|
_setFileIntervals(value) {
|
|
25
25
|
this.db.set("fileIntervals", value);
|
|
26
26
|
},
|
|
27
|
-
|
|
27
|
+
filterByMinimumInterval(files) {
|
|
28
28
|
const interval = this.perFileInterval;
|
|
29
|
-
if (!interval)
|
|
29
|
+
if (!interval) {
|
|
30
|
+
return files;
|
|
31
|
+
}
|
|
30
32
|
|
|
31
|
-
const
|
|
32
|
-
const
|
|
33
|
+
const minTimestamp = Date.now() - (interval * 1000 * 60);
|
|
34
|
+
const savedData = this._getFileIntervals();
|
|
35
|
+
return files.filter(({ id }) => !savedData[id] || savedData[id] < minTimestamp);
|
|
36
|
+
},
|
|
37
|
+
recordFileEmits(fileIds) {
|
|
38
|
+
if (!this.perFileInterval || !fileIds.length) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
33
41
|
|
|
42
|
+
const now = Date.now();
|
|
43
|
+
const minTimestamp = now - (this.perFileInterval * 1000 * 60);
|
|
34
44
|
const savedData = this._getFileIntervals();
|
|
35
45
|
Object.entries(savedData).forEach(([
|
|
36
46
|
key,
|
|
37
47
|
value,
|
|
38
48
|
]) => {
|
|
39
|
-
if (value < minTimestamp)
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const filteredFiles = files.filter(({ id }) => {
|
|
43
|
-
const exists = !!savedData[id];
|
|
44
|
-
if (!exists) {
|
|
45
|
-
savedData[id] = now;
|
|
49
|
+
if (value < minTimestamp) {
|
|
50
|
+
delete savedData[key];
|
|
46
51
|
}
|
|
47
|
-
|
|
52
|
+
});
|
|
53
|
+
fileIds.forEach((id) => {
|
|
54
|
+
savedData[id] = now;
|
|
48
55
|
});
|
|
49
56
|
this._setFileIntervals(savedData);
|
|
57
|
+
},
|
|
58
|
+
checkMinimumInterval(files) {
|
|
59
|
+
const filteredFiles = this.filterByMinimumInterval(files);
|
|
60
|
+
this.recordFileEmits(filteredFiles.map(({ id }) => id));
|
|
50
61
|
return filteredFiles;
|
|
51
62
|
},
|
|
52
63
|
},
|
|
@@ -122,6 +122,12 @@ export default {
|
|
|
122
122
|
getUpdateTypes() {
|
|
123
123
|
return [];
|
|
124
124
|
},
|
|
125
|
+
/**
|
|
126
|
+
* File fields to request per change; `undefined` returns the API's minimal fields.
|
|
127
|
+
*/
|
|
128
|
+
getChangesFileFields() {
|
|
129
|
+
return undefined;
|
|
130
|
+
},
|
|
125
131
|
/**
|
|
126
132
|
* This method is responsible for processing a list of changed files
|
|
127
133
|
* according to the event source's purpose. As an abstract method, it must
|
|
@@ -201,7 +207,12 @@ export default {
|
|
|
201
207
|
|
|
202
208
|
const driveId = this.getDriveId();
|
|
203
209
|
const changedFilesStream =
|
|
204
|
-
this.googleDrive.listChanges(
|
|
210
|
+
this.googleDrive.listChanges(
|
|
211
|
+
pageToken,
|
|
212
|
+
driveId,
|
|
213
|
+
this.changesPageSize,
|
|
214
|
+
this.getChangesFileFields(),
|
|
215
|
+
);
|
|
205
216
|
for await (const changedFilesPage of changedFilesStream) {
|
|
206
217
|
const {
|
|
207
218
|
changedFiles,
|
|
@@ -6,7 +6,7 @@ export default {
|
|
|
6
6
|
key: "google_drive-new-access-proposal",
|
|
7
7
|
name: "New Access Proposal",
|
|
8
8
|
description: "Emit new event when a new access proposal is requested in Google Drive",
|
|
9
|
-
version: "0.0.
|
|
9
|
+
version: "0.0.19",
|
|
10
10
|
type: "source",
|
|
11
11
|
dedupe: "unique",
|
|
12
12
|
props: {
|
|
@@ -11,7 +11,7 @@ export default {
|
|
|
11
11
|
key: "google_drive-new-files-instant",
|
|
12
12
|
name: "New Files (Instant)",
|
|
13
13
|
description: "Emit new event when a new file is added in your linked Google Drive",
|
|
14
|
-
version: "0.2.
|
|
14
|
+
version: "0.2.16",
|
|
15
15
|
type: "source",
|
|
16
16
|
dedupe: "unique",
|
|
17
17
|
props: {
|
|
@@ -8,7 +8,7 @@ export default {
|
|
|
8
8
|
key: "google_drive-new-files-instant-polling",
|
|
9
9
|
name: "New Files (Polling)",
|
|
10
10
|
description: "Emit new event when a new file is added in your linked Google Drive",
|
|
11
|
-
version: "0.0.
|
|
11
|
+
version: "0.0.14",
|
|
12
12
|
type: "source",
|
|
13
13
|
dedupe: "unique",
|
|
14
14
|
props: {
|
|
@@ -7,7 +7,7 @@ export default {
|
|
|
7
7
|
key: "google_drive-new-files-shared-drive",
|
|
8
8
|
name: "New Files (Shared Drive)",
|
|
9
9
|
description: "Emit new event when a new file is added in your shared Google Drive",
|
|
10
|
-
version: "0.1.
|
|
10
|
+
version: "0.1.16",
|
|
11
11
|
type: "source",
|
|
12
12
|
dedupe: "unique",
|
|
13
13
|
props: {
|
|
@@ -18,7 +18,7 @@ export default {
|
|
|
18
18
|
name: "New or Modified Comments (Instant)",
|
|
19
19
|
description:
|
|
20
20
|
"Emit new event when a comment is created or modified in the selected file",
|
|
21
|
-
version: "1.0.
|
|
21
|
+
version: "1.0.25",
|
|
22
22
|
type: "source",
|
|
23
23
|
// Dedupe events based on the "x-goog-message-number" header for the target channel:
|
|
24
24
|
// https://developers.google.com/drive/api/v3/push#making-watch-requests
|
|
@@ -7,7 +7,7 @@ export default {
|
|
|
7
7
|
key: "google_drive-new-or-modified-comments-polling",
|
|
8
8
|
name: "New or Modified Comments (Polling)",
|
|
9
9
|
description: "Emit new event when a comment is created or modified in the selected file",
|
|
10
|
-
version: "0.0.
|
|
10
|
+
version: "0.0.14",
|
|
11
11
|
type: "source",
|
|
12
12
|
dedupe: "unique",
|
|
13
13
|
props: {
|
|
@@ -9,15 +9,17 @@
|
|
|
9
9
|
// 2) A timer that runs on regular intervals, renewing the notification channel as needed
|
|
10
10
|
|
|
11
11
|
import {
|
|
12
|
+
CHANGED_FILE_FIELDS,
|
|
13
|
+
GOOGLE_DRIVE_MIME_TYPE_PREFIX,
|
|
12
14
|
GOOGLE_DRIVE_NOTIFICATION_ADD,
|
|
13
15
|
GOOGLE_DRIVE_NOTIFICATION_CHANGE,
|
|
14
16
|
GOOGLE_DRIVE_NOTIFICATION_UPDATE,
|
|
17
|
+
PDF_EXPORTABLE_MIME_TYPES,
|
|
15
18
|
} from "../../common/constants.mjs";
|
|
16
19
|
import commonDedupeChanges from "../common-dedupe-changes.mjs";
|
|
17
20
|
import common from "../common-webhook.mjs";
|
|
18
21
|
import { stashFile } from "../../common/utils.mjs";
|
|
19
22
|
import sampleEmit from "./test-event.mjs";
|
|
20
|
-
import md5 from "md5";
|
|
21
23
|
|
|
22
24
|
const { googleDrive } = common.props;
|
|
23
25
|
|
|
@@ -26,10 +28,8 @@ export default {
|
|
|
26
28
|
key: "google_drive-new-or-modified-files",
|
|
27
29
|
name: "New or Modified Files (Instant)",
|
|
28
30
|
description: "Emit new event when a file in the selected Drive is created, modified or trashed.",
|
|
29
|
-
version: "0.
|
|
31
|
+
version: "1.0.0",
|
|
30
32
|
type: "source",
|
|
31
|
-
// Dedupe events based on the "x-goog-message-number" header for the target channel:
|
|
32
|
-
// https://developers.google.com/drive/api/v3/push#making-watch-requests
|
|
33
33
|
dedupe: "unique",
|
|
34
34
|
props: {
|
|
35
35
|
...common.props,
|
|
@@ -66,7 +66,7 @@ export default {
|
|
|
66
66
|
includeLink: {
|
|
67
67
|
label: "Include Link",
|
|
68
68
|
type: "boolean",
|
|
69
|
-
description: "Upload file to your File Stash and emit temporary download link to the file. Google Workspace documents will be converted to PDF. See [the docs](https://pipedream.com/docs/connect/components/files) to learn more about working with files in Pipedream.",
|
|
69
|
+
description: "Upload file to your File Stash and emit temporary download link to the file. Google Workspace documents will be converted to PDF. Files that can't be downloaded emit `fileURLError` instead. See [the docs](https://pipedream.com/docs/connect/components/files) to learn more about working with files in Pipedream.",
|
|
70
70
|
default: false,
|
|
71
71
|
optional: true,
|
|
72
72
|
},
|
|
@@ -113,31 +113,25 @@ export default {
|
|
|
113
113
|
GOOGLE_DRIVE_NOTIFICATION_UPDATE,
|
|
114
114
|
];
|
|
115
115
|
},
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
const ts = Date.parse(tsString);
|
|
123
|
-
const eventId = headers && headers["x-goog-message-number"];
|
|
124
|
-
|
|
116
|
+
getChangesFileFields() {
|
|
117
|
+
return CHANGED_FILE_FIELDS;
|
|
118
|
+
},
|
|
119
|
+
generateMeta({
|
|
120
|
+
id, name, modifiedTime, trashed,
|
|
121
|
+
}) {
|
|
125
122
|
return {
|
|
126
|
-
id:
|
|
127
|
-
summary,
|
|
128
|
-
ts,
|
|
123
|
+
id: `${id}-${modifiedTime}-${trashed}`,
|
|
124
|
+
summary: name,
|
|
125
|
+
ts: Date.parse(modifiedTime),
|
|
129
126
|
};
|
|
130
127
|
},
|
|
131
|
-
|
|
128
|
+
getChanges(headers) {
|
|
132
129
|
if (!headers) {
|
|
133
130
|
return {
|
|
134
|
-
change: {
|
|
131
|
+
change: {},
|
|
135
132
|
};
|
|
136
133
|
}
|
|
137
|
-
const resourceUri = headers["x-goog-resource-uri"];
|
|
138
|
-
const metadata = await this.googleDrive.getFileMetadata(`${resourceUri}&fields=*`);
|
|
139
134
|
return {
|
|
140
|
-
...metadata,
|
|
141
135
|
change: {
|
|
142
136
|
state: headers["x-goog-resource-state"],
|
|
143
137
|
resourceURI: headers["x-goog-resource-uri"],
|
|
@@ -145,30 +139,53 @@ export default {
|
|
|
145
139
|
},
|
|
146
140
|
};
|
|
147
141
|
},
|
|
142
|
+
async getFileLink(file) {
|
|
143
|
+
const { mimeType } = file;
|
|
144
|
+
if (mimeType.startsWith(GOOGLE_DRIVE_MIME_TYPE_PREFIX)
|
|
145
|
+
&& !PDF_EXPORTABLE_MIME_TYPES.includes(mimeType)) {
|
|
146
|
+
return {
|
|
147
|
+
fileURLError: `Files of type ${mimeType} can't be downloaded`,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
try {
|
|
151
|
+
return {
|
|
152
|
+
fileURL: await stashFile(file, this.googleDrive, this.dir),
|
|
153
|
+
};
|
|
154
|
+
} catch (error) {
|
|
155
|
+
// Isolate per-file failures so one file can't block the page token; rate limits still retry
|
|
156
|
+
if (this.googleDrive.isRetryableError(error, error.status || error.response?.status)) {
|
|
157
|
+
throw error;
|
|
158
|
+
}
|
|
159
|
+
console.log(`Could not upload file ${file.name} to the File Stash: ${error.message}`);
|
|
160
|
+
return {
|
|
161
|
+
fileURLError: error.message,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
},
|
|
148
165
|
async processChanges(changedFiles, headers) {
|
|
149
|
-
const changes =
|
|
150
|
-
|
|
151
|
-
const
|
|
166
|
+
const changes = this.getChanges(headers);
|
|
167
|
+
const filteredFiles = this.filterByMinimumInterval(changedFiles);
|
|
168
|
+
const emittedFileIds = [];
|
|
152
169
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
170
|
+
try {
|
|
171
|
+
for (const file of filteredFiles) {
|
|
172
|
+
if (!this.shouldProcess(file)) {
|
|
173
|
+
console.log(`Skipping file ${file.name}`);
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
157
176
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
if (this.includeLink) {
|
|
168
|
-
eventToEmit.fileURL = await stashFile(file, this.googleDrive, this.dir);
|
|
177
|
+
const eventToEmit = {
|
|
178
|
+
file,
|
|
179
|
+
...changes,
|
|
180
|
+
};
|
|
181
|
+
if (this.includeLink) {
|
|
182
|
+
Object.assign(eventToEmit, await this.getFileLink(file));
|
|
183
|
+
}
|
|
184
|
+
this.$emit(eventToEmit, this.generateMeta(file));
|
|
185
|
+
emittedFileIds.push(file.id);
|
|
169
186
|
}
|
|
170
|
-
|
|
171
|
-
this
|
|
187
|
+
} finally {
|
|
188
|
+
this.recordFileEmits(emittedFileIds);
|
|
172
189
|
}
|
|
173
190
|
},
|
|
174
191
|
},
|
|
@@ -1 +1,30 @@
|
|
|
1
|
-
export default
|
|
1
|
+
export default {
|
|
2
|
+
"file": {
|
|
3
|
+
"kind": "drive#file",
|
|
4
|
+
"id": "1eWLQU_76tvfXptBGkdnjmPbpwhrOtkYS",
|
|
5
|
+
"name": "Quarterly Report.csv",
|
|
6
|
+
"mimeType": "text/csv",
|
|
7
|
+
"parents": [
|
|
8
|
+
"1jPHKePO9_B4jDvpBFyAs38Ji_tCDdjxI",
|
|
9
|
+
],
|
|
10
|
+
"createdTime": "2026-09-28T17:51:38.475Z",
|
|
11
|
+
"modifiedTime": "2026-09-28T18:04:12.918Z",
|
|
12
|
+
"trashed": false,
|
|
13
|
+
"version": "4",
|
|
14
|
+
"size": "2048",
|
|
15
|
+
"md5Checksum": "5d41402abc4b2a76b9719d911017c592",
|
|
16
|
+
"webViewLink": "https://drive.google.com/file/d/1eWLQU_76tvfXptBGkdnjmPbpwhrOtkYS/view?usp=drivesdk",
|
|
17
|
+
"lastModifyingUser": {
|
|
18
|
+
"kind": "drive#user",
|
|
19
|
+
"displayName": "John Doe",
|
|
20
|
+
"me": true,
|
|
21
|
+
"permissionId": "07742336189483",
|
|
22
|
+
"emailAddress": "john@doe.com",
|
|
23
|
+
"photoLink": "https://lh3.googleusercontent.com/a/default-user=s64",
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
"change": {
|
|
27
|
+
"state": "change",
|
|
28
|
+
"resourceURI": "https://www.googleapis.com/drive/v3/changes?alt=json&pageToken=1794",
|
|
29
|
+
},
|
|
30
|
+
};
|
|
@@ -9,7 +9,7 @@ export default {
|
|
|
9
9
|
key: "google_drive-new-or-modified-files-polling",
|
|
10
10
|
name: "New or Modified Files (Polling)",
|
|
11
11
|
description: "Emit new event when a file in the selected Drive is created, modified or trashed. [See the documentation](https://developers.google.com/drive/api/v3/reference/changes/list)",
|
|
12
|
-
version: "0.0.
|
|
12
|
+
version: "0.0.16",
|
|
13
13
|
type: "source",
|
|
14
14
|
dedupe: "unique",
|
|
15
15
|
props: {
|
|
@@ -21,7 +21,7 @@ export default {
|
|
|
21
21
|
key: "google_drive-new-or-modified-folders",
|
|
22
22
|
name: "New or Modified Folders (Instant)",
|
|
23
23
|
description: "Emit new event when a folder is created or modified in the selected Drive",
|
|
24
|
-
version: "0.2.
|
|
24
|
+
version: "0.2.19",
|
|
25
25
|
type: "source",
|
|
26
26
|
// Dedupe events based on the "x-goog-message-number" header for the target channel:
|
|
27
27
|
// https://developers.google.com/drive/api/v3/push#making-watch-requests
|
|
@@ -12,7 +12,7 @@ export default {
|
|
|
12
12
|
key: "google_drive-new-or-modified-folders-polling",
|
|
13
13
|
name: "New or Modified Folders (Polling)",
|
|
14
14
|
description: "Emit new event when a folder is created or modified in the selected Drive",
|
|
15
|
-
version: "0.0.
|
|
15
|
+
version: "0.0.15",
|
|
16
16
|
type: "source",
|
|
17
17
|
dedupe: "unique",
|
|
18
18
|
props: {
|
|
@@ -6,7 +6,7 @@ export default {
|
|
|
6
6
|
type: "source",
|
|
7
7
|
name: "New Spreadsheet (Instant)",
|
|
8
8
|
description: "Emit new event when a new spreadsheet is created in a drive.",
|
|
9
|
-
version: "0.1.
|
|
9
|
+
version: "0.1.29",
|
|
10
10
|
props: {
|
|
11
11
|
googleDrive: newFilesInstant.props.googleDrive,
|
|
12
12
|
db: newFilesInstant.props.db,
|
|
@@ -8,7 +8,7 @@ export default {
|
|
|
8
8
|
key: "google_drive-new-spreadsheet-polling",
|
|
9
9
|
name: "New Spreadsheet (Polling)",
|
|
10
10
|
description: "Emit new event when a new spreadsheet is created in a drive.",
|
|
11
|
-
version: "0.0.
|
|
11
|
+
version: "0.0.14",
|
|
12
12
|
type: "source",
|
|
13
13
|
dedupe: "unique",
|
|
14
14
|
props: {
|