@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,68 @@
1
+ import googleDrive from "../../google_drive.app.mjs";
2
+
3
+ export default {
4
+ key: "google_drive-new-shared-drive",
5
+ name: "New Shared Drive",
6
+ description: "Emits a new event any time a shared drive is created.",
7
+ version: "0.0.9",
8
+ type: "source",
9
+ dedupe: "unique",
10
+ props: {
11
+ googleDrive,
12
+ db: "$.service.db",
13
+ timer: {
14
+ label: "Polling interval",
15
+ description: "Interval to poll the Google Drive API for new shared drives",
16
+ type: "$.interface.timer",
17
+ default: {
18
+ intervalSeconds: 60 * 15, // 30 minutes
19
+ },
20
+ },
21
+ },
22
+ hooks: {
23
+ async deploy() {
24
+ const { drives: initDrives } = await this.googleDrive.listDrivesInPage();
25
+ for (const drive of initDrives) {
26
+ const newDrive = await this.googleDrive.getDrive(drive.id);
27
+ const meta = this.generateMeta(newDrive);
28
+ this.$emit(newDrive, meta);
29
+ }
30
+
31
+ this._setKnownDrives(initDrives.map((drive) => drive.id));
32
+ },
33
+ },
34
+ methods: {
35
+ _getKnownDrives() {
36
+ return this.db.get("driveIds");
37
+ },
38
+ _setKnownDrives(driveIds) {
39
+ this.db.set("driveIds", Array.from(driveIds));
40
+ },
41
+ generateMeta(drive) {
42
+ const ts = new Date(drive.createdTime).getTime();
43
+ return {
44
+ id: drive.id,
45
+ summary: drive.name,
46
+ ts,
47
+ };
48
+ },
49
+ },
50
+ async run() {
51
+ const knownDrives = new Set(this._getKnownDrives());
52
+ const drivesStream = this.googleDrive.listDrives();
53
+ for await (const drive of drivesStream) {
54
+ if (knownDrives.has(drive.id)) {
55
+ // We've already seen this drive, so we skip it
56
+ continue;
57
+ }
58
+
59
+ knownDrives.add(drive.id);
60
+
61
+ const newDrive = await this.googleDrive.getDrive(drive.id);
62
+ const meta = this.generateMeta(newDrive);
63
+ this.$emit(newDrive, meta);
64
+ }
65
+
66
+ this._setKnownDrives(knownDrives);
67
+ },
68
+ };
package/utils.mjs ADDED
@@ -0,0 +1,247 @@
1
+ import fs from "fs";
2
+ import { axios } from "@pipedream/platform";
3
+ import {
4
+ MY_DRIVE_VALUE,
5
+ LEGACY_MY_DRIVE_VALUE,
6
+ MAX_FILE_OPTION_PATH_SEGMENTS,
7
+ } from "./constants.mjs";
8
+
9
+ /**
10
+ * Returns whether the specified drive ID corresponds to the authenticated
11
+ * user's My Drive or not
12
+ *
13
+ * @param {String} drive the ID value of a Google Drive
14
+ * @returns `true` only when the specified drive is the user's 'My Drive'
15
+ */
16
+ function isMyDrive(drive) {
17
+ return drive === MY_DRIVE_VALUE || drive === LEGACY_MY_DRIVE_VALUE;
18
+ }
19
+
20
+ /**
21
+ * Returns a valid Google Drive ID to be used in Google Drive API calls
22
+ *
23
+ * @param {String} drive the ID value of a Google Drive, as provided by the
24
+ * `drive` prop definition of this app
25
+ * @returns the proper Google Drive ID to be used in Google Drive API calls
26
+ */
27
+ function getDriveId(drive) {
28
+ return isMyDrive(drive)
29
+ ? null
30
+ : drive;
31
+ }
32
+
33
+ /**
34
+ * Gets an options object to be used in functions that use the
35
+ * [the `drive.drives.list` API](https://bit.ly/3AiWE1x).
36
+ *
37
+ * @param {String} drive the ID value of a Google Drive, as provided by the
38
+ * `drive` prop definition of this app
39
+ * @param {object} [baseOpts = {}] - an object containing extra/optional
40
+ * parameters to be fed to the GDrive API call, as defined in [the API
41
+ * docs](https://bit.ly/3AnQDR1)
42
+ *
43
+ * @returns an object containing the options
44
+ */
45
+ function getListFilesOpts(drive, baseOpts = {}) {
46
+ // Use default options (e.g., `corpora=drive`) for `files.list` if `drive` is
47
+ // empty or is "My Drive". Otherwise, use the "drive" corpus and include
48
+ // `supportsAllDrives` param.
49
+ const opts = (!drive || isMyDrive(drive))
50
+ ? baseOpts
51
+ : {
52
+ ...baseOpts,
53
+ corpora: "drive",
54
+ driveId: getDriveId(drive),
55
+ includeItemsFromAllDrives: true,
56
+ supportsAllDrives: true,
57
+ };
58
+ return opts;
59
+ }
60
+
61
+ /**
62
+ * Returns a file stream from a file URL or file path to be used in Google Drive
63
+ * API calls
64
+ *
65
+ * @param {Object} opts - an object containing options for getting a file stream
66
+ * @param {String} opts.fileUrl - the url of a file to download to a Readable
67
+ * stream
68
+ * @param {String} opts.filePath - the path to a file from which to create a
69
+ * Readable stream
70
+ * @returns {stream.Readable} a Readable stream from the file URL or file path
71
+ */
72
+ async function getFileStream({
73
+ $, fileUrl, filePath,
74
+ }) {
75
+ return fileUrl
76
+ ? (await axios($ ?? this, {
77
+ url: fileUrl,
78
+ method: "GET",
79
+ responseType: "stream",
80
+ }))
81
+ : fs.createReadStream(filePath);
82
+ }
83
+
84
+ /**
85
+ * Truncate an array of path segments from its base
86
+ *
87
+ * @param {String[]} pathArr - the array of path segments
88
+ * @returns the truncated array whose first element is "..." if truncated
89
+ */
90
+ function truncatePath(pathArr) {
91
+ if (pathArr.length <= MAX_FILE_OPTION_PATH_SEGMENTS) {
92
+ return pathArr;
93
+ }
94
+ return [
95
+ "...",
96
+ ...pathArr.slice(-1 * (MAX_FILE_OPTION_PATH_SEGMENTS - 1)),
97
+ ];
98
+ }
99
+
100
+ /**
101
+ * Builds an object mapping file IDs to arrays of file/folder ID path segments from the drive's root
102
+ * folder to each file, using the `file.parents` property and a list of folders in the drive
103
+ *
104
+ * @see
105
+ * {@link https://developers.google.com/drive/api/v3/reference/files Google Drive File Resource}
106
+ *
107
+ * @param {object[]} files - the array of files for which to build paths
108
+ * @param {object[]} folders - the array of folders in the drive
109
+ * @returns {Object.<string, string[]>} the object mapping file IDs to arrays of path segments
110
+ */
111
+ function buildFilePaths(files = [], folders = []) {
112
+ const folderIdToFolder = folders.reduce((acc, cur) => {
113
+ acc[cur.id] = cur;
114
+ return acc;
115
+ }, {});
116
+ const paths = {};
117
+ // Recursive function that returns an array of file `id`s representing the path to a file if
118
+ // requisite parent folders are available (in `file.parents`) to the requesting user, or an array
119
+ // containing the file ID otherwise
120
+ const pathToFile = (file) => {
121
+ if (!file) {
122
+ // unretrieved folder or root folder
123
+ return [];
124
+ }
125
+ if (paths[file.id] !== undefined) {
126
+ return paths[file.id];
127
+ }
128
+ if (!file.parents) {
129
+ // file belongs to a different drive and user does not have access to the parent
130
+ return [
131
+ file.id,
132
+ ];
133
+ }
134
+ let parentPath;
135
+ for (const parent of file.parents) {
136
+ parentPath = pathToFile(folderIdToFolder[parent]);
137
+ paths[parent] = parentPath;
138
+ if (parentPath?.[0]) {
139
+ break;
140
+ }
141
+ }
142
+ return [
143
+ ...parentPath,
144
+ file.id,
145
+ ];
146
+ };
147
+ files.forEach((file) => {
148
+ paths[file.id] = pathToFile(file);
149
+ });
150
+ return paths;
151
+ }
152
+
153
+ /**
154
+ * Builds an object mapping file IDs to arrays of file/folder name path segments from the drive's
155
+ * root folder to each file, using the `file.parents` property and a list of folders in the drive
156
+ *
157
+ * @param {object[]} files - the array of files for which to build paths
158
+ * @param {object[]} folders - the array of folders in the drive
159
+ * @returns {Object.<string, string[]>} the object mapping file IDs to arrays of path segments
160
+ */
161
+ function buildFileNamePaths(files = [], folders = []) {
162
+ const fileIdToFile = files.concat(folders).reduce((acc, cur) => {
163
+ acc[cur.id] = cur;
164
+ return acc;
165
+ }, {});
166
+ const fileIdToPath = buildFilePaths(files, folders);
167
+ return Object.fromEntries(Object.entries(fileIdToPath).map(([
168
+ id,
169
+ path,
170
+ ]) => ([
171
+ id,
172
+ path.filter((id) => fileIdToFile[id]?.name)
173
+ .map((id) => fileIdToFile[id]?.name),
174
+ ])));
175
+ }
176
+
177
+ /**
178
+ * Gets an object mapping file IDs to string paths from the drive's root folder to each file, if the
179
+ * file's `parents` are available to the requesting user
180
+ *
181
+ * @param {object[]} files - the array of files for which to get file paths
182
+ * @param {object[]} folders - the array of folders in the drive
183
+ * @returns {Object.<string, string>} the object mapping file IDs to file paths
184
+ */
185
+ function getFilePaths(files = [], folders = []) {
186
+ const fileIdToNamePath = buildFileNamePaths(files, folders);
187
+ return Object.fromEntries(Object.entries(fileIdToNamePath).map(([
188
+ id,
189
+ path,
190
+ ]) => ([
191
+ id,
192
+ truncatePath(path).join(" > "),
193
+ ])));
194
+ }
195
+
196
+ /**
197
+ * Return an object compose of non-empty string valued properties of `obj`
198
+ *
199
+ * @param {Object} obj - the source object
200
+ * @param {String[]} [fromKeys] - keys of properties in `obj` to omit if corresponding value is
201
+ * empty string, or all keys by default
202
+ * @returns the new object
203
+ */
204
+ function omitEmptyStringValues(obj, fromKeys) {
205
+ return Object.fromEntries(
206
+ // eslint-disable-next-line multiline-ternary,array-element-newline,array-bracket-newline
207
+ Object.entries(obj).filter(([ k, v ]) => {
208
+ return (fromKeys && !fromKeys.includes(k)) || v !== "";
209
+ }),
210
+ );
211
+ }
212
+
213
+ /**
214
+ * A utility function that accepts a string as an argument and reformats it in
215
+ * order to remove newline characters and consecutive spaces. Useful when
216
+ * dealing with very long templated strings that are split into multiple lines.
217
+ *
218
+ * @example
219
+ * // returns "This is a much cleaner string"
220
+ * toSingleLineString(`
221
+ * This is a much
222
+ * cleaner string
223
+ * `);
224
+ *
225
+ * @param {string} multiLineString the input string to reformat
226
+ * @returns a formatted string based on the content of the input argument,
227
+ * without newlines and multiple spaces
228
+ * Source: {@linkcode ../aws/sources/common/utils.mjs utils.mjs}.
229
+ */
230
+ function toSingleLineString(multiLineString) {
231
+ return multiLineString
232
+ .trim()
233
+ .replace(/\n/g, " ")
234
+ .replace(/\s{2,}/g, " ");
235
+ }
236
+
237
+ export {
238
+ MY_DRIVE_VALUE,
239
+ isMyDrive,
240
+ getDriveId,
241
+ getListFilesOpts,
242
+ getFileStream,
243
+ omitEmptyStringValues,
244
+ toSingleLineString,
245
+ buildFilePaths,
246
+ getFilePaths,
247
+ };
package/LICENSE DELETED
@@ -1,7 +0,0 @@
1
- Copyright 2020 Pipedream, Inc.
2
-
3
- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
-
5
- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
-
7
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -1,212 +0,0 @@
1
- const axios = require("axios");
2
- const { google } = require("googleapis");
3
-
4
- const GOOGLE_DRIVE_UPDATE_TYPES = [
5
- "add",
6
- "sync",
7
- "remove",
8
- "update",
9
- "trash",
10
- "untrash",
11
- "change",
12
- ];
13
-
14
- module.exports = {
15
- type: "app",
16
- app: "google_drive",
17
- propDefinitions: {
18
- watchedDrive: {
19
- type: "string",
20
- label: "Drive",
21
- description: "The drive you want to watch for changes",
22
- async options({ page, prevContext }) {
23
- const { nextPageToken } = prevContext;
24
- return await this.listDrives(nextPageToken);
25
- },
26
- },
27
- updateTypes: {
28
- type: "string[]",
29
- label: "Types of updates",
30
- description:
31
- "The types of updates you want to watch for on these files. [See Google's docs](https://developers.google.com/drive/api/v3/push#understanding-drive-api-notification-events).",
32
- // https://developers.google.com/drive/api/v3/push#understanding-drive-api-notification-events
33
- default: GOOGLE_DRIVE_UPDATE_TYPES,
34
- options: GOOGLE_DRIVE_UPDATE_TYPES,
35
- },
36
- watchForPropertiesChanges: {
37
- type: "boolean",
38
- label: "Watch for changes to file properties",
39
- description:
40
- "Watch for changes to [file properties](https://developers.google.com/drive/api/v3/properties) in addition to changes to content. **Defaults to `false`, watching for only changes to content**.",
41
- optional: true,
42
- default: false,
43
- },
44
- },
45
- methods: {
46
- // Returns a drive object authenticated with the user's access token
47
- drive() {
48
- const auth = new google.auth.OAuth2();
49
- auth.setCredentials({ access_token: this.$auth.oauth_access_token });
50
- return google.drive({ version: "v3", auth });
51
- },
52
- // Google's push notifications provide a URL to the resource that changed,
53
- // which we can use to fetch the file's metadata. So we use axios here
54
- // (vs. the Node client) to get that.
55
- async getFileMetadata(url) {
56
- return (
57
- await axios({
58
- method: "GET",
59
- headers: {
60
- Authorization: `Bearer ${this.$auth.oauth_access_token}`,
61
- },
62
- url,
63
- })
64
- ).data;
65
- },
66
- async getChanges(pageToken, driveId) {
67
- const drive = this.drive();
68
- // As with many of the methods for Google Drive, we must
69
- // pass a request of a different shape when we're requesting
70
- // changes for My Drive (null driveId) vs. a shared drive
71
- let changeRequest;
72
- if (driveId) {
73
- changeRequest = {
74
- driveId,
75
- pageToken,
76
- includeItemsFromAllDrives: true,
77
- supportsAllDrives: true,
78
- };
79
- } else {
80
- changeRequest = {
81
- pageToken,
82
- };
83
- }
84
- const { changes, newStartPageToken } = (
85
- await drive.changes.list(changeRequest)
86
- ).data;
87
-
88
- // Some changes do not include an associated file object. Return only those that do
89
- const changedFiles = changes
90
- .map((change) => change.file)
91
- .filter((f) => typeof f === "object");
92
-
93
- return {
94
- changedFiles,
95
- newStartPageToken,
96
- };
97
- },
98
- async getPageToken() {
99
- const drive = this.drive();
100
- return (await drive.changes.getStartPageToken({})).data.startPageToken;
101
- },
102
- async listDrives(pageToken) {
103
- const drive = this.drive();
104
- const resp = await drive.drives.list({ pageToken });
105
- const { drives, nextPageToken } = resp.data;
106
- // "My Drive" isn't returned from the list of drives,
107
- // so we add it to the list and assign it a static
108
- // ID that we can refer to when we need.
109
- const options = [{ label: "My Drive", value: "myDrive" }];
110
- for (const d of drives) {
111
- options.push({ label: d.name, value: d.id });
112
- }
113
- return {
114
- options,
115
- context: { nextPageToken },
116
- };
117
- },
118
- async listFiles(opts) {
119
- const drive = this.drive();
120
- // Listing files in My Drive and a shared drive requires
121
- // mutually-exclusive options, so we accept an object of
122
- // opts from the caller and pass them to the list method.
123
- const resp = await drive.files.list(opts);
124
- const { files, nextPageToken } = resp.data;
125
- const options = files.map((file) => {
126
- return { label: file.name, value: file.id };
127
- });
128
- return {
129
- options,
130
- context: { nextPageToken },
131
- };
132
- },
133
- _makeWatchRequestBody(id, address) {
134
- return {
135
- id, // the component-specific channel ID, a UUID
136
- type: "web_hook",
137
- address, // the component-specific HTTP endpoint
138
- };
139
- },
140
- async watchDrive(id, address, pageToken, driveId) {
141
- const drive = this.drive();
142
- const requestBody = this._makeWatchRequestBody(id, address);
143
-
144
- // Google expects an entirely different object to be passed
145
- // when you make a watch request for My Drive vs. a shared drive
146
- // "My Drive" doesn't have a driveId, so if this method is called
147
- // without a driveId, we make a watch request for My Drive
148
- let watchRequest;
149
- if (driveId) {
150
- watchRequest = {
151
- driveId,
152
- pageToken,
153
- requestBody,
154
- includeItemsFromAllDrives: true,
155
- supportsAllDrives: true,
156
- };
157
- } else {
158
- watchRequest = {
159
- pageToken,
160
- requestBody,
161
- };
162
- }
163
- // When watching for changes to an entire account, we must pass a pageToken,
164
- // which points to the moment in time we want to start watching for changes:
165
- // https://developers.google.com/drive/api/v3/manage-changes
166
- const { expiration, resourceId } = (
167
- await drive.changes.watch(watchRequest)
168
- ).data;
169
- console.log(`Watch request for drive successful, expiry: ${expiration}`);
170
- return {
171
- expiration: parseInt(expiration),
172
- resourceId,
173
- };
174
- },
175
- async watchFile(id, address, fileId) {
176
- const drive = this.drive();
177
- const requestBody = this._makeWatchRequestBody(id, address);
178
- const { expiration, resourceId } = (
179
- await drive.files.watch({
180
- fileId,
181
- requestBody,
182
- supportsAllDrives: true,
183
- })
184
- ).data;
185
- console.log(
186
- `Watch request for file ${fileId} successful, expiry: ${expiration}`
187
- );
188
- return {
189
- expiration: parseInt(expiration),
190
- resourceId,
191
- };
192
- },
193
- async stopNotifications(id, resourceId) {
194
- // id = channelID
195
- // See https://github.com/googleapis/google-api-nodejs-client/issues/627
196
- const drive = this.drive();
197
-
198
- // If for some reason the channel doesn't exist, this throws an error
199
- // It's OK for this to fail in those cases, since we'll renew the channel
200
- // immediately after trying to stop it if we still want notifications,
201
- // so we squash the error, log it, and move on.
202
- try {
203
- await drive.channels.stop({ resource: { id, resourceId } });
204
- console.log(`Stopped push notifications on channel ${id}`);
205
- } catch (err) {
206
- console.error(
207
- `Failed to stop channel ${id} for resource ${resourceId}: ${err}`
208
- );
209
- }
210
- },
211
- },
212
- };