diff --git a/packages/nodes-base/credentials/GoogleDriveOAuth2Api.credentials.ts b/packages/nodes-base/credentials/GoogleDriveOAuth2Api.credentials.ts
index 1f48245e56..a5b5312705 100644
--- a/packages/nodes-base/credentials/GoogleDriveOAuth2Api.credentials.ts
+++ b/packages/nodes-base/credentials/GoogleDriveOAuth2Api.credentials.ts
@@ -22,5 +22,12 @@ export class GoogleDriveOAuth2Api implements ICredentialType {
type: 'hidden',
default: scopes.join(' '),
},
+ {
+ displayName:
+ 'Make sure that you have enabled the Google Drive API in the Google Cloud Console. More info.',
+ name: 'notice',
+ type: 'notice',
+ default: '',
+ },
];
}
diff --git a/packages/nodes-base/nodes/Google/Drive/GoogleDrive.node.ts b/packages/nodes-base/nodes/Google/Drive/GoogleDrive.node.ts
index fe3169946b..ed2dde9c54 100644
--- a/packages/nodes-base/nodes/Google/Drive/GoogleDrive.node.ts
+++ b/packages/nodes-base/nodes/Google/Drive/GoogleDrive.node.ts
@@ -1,2738 +1,27 @@
-import type {
- IDataObject,
- IExecuteFunctions,
- INodeExecutionData,
- INodeType,
- INodeTypeDescription,
-} from 'n8n-workflow';
-import { BINARY_ENCODING } from 'n8n-workflow';
-
-import { googleApiRequest, googleApiRequestAllItems } from './GenericFunctions';
-
-import { v4 as uuid } from 'uuid';
-import type { Readable } from 'stream';
-import { driveSearch, fileSearch, folderSearch } from './SearchFunctions';
-
-const UPLOAD_CHUNK_SIZE = 256 * 1024;
-
-export class GoogleDrive implements INodeType {
- description: INodeTypeDescription = {
- displayName: 'Google Drive',
- name: 'googleDrive',
- icon: 'file:googleDrive.svg',
- group: ['input'],
- version: [1, 2],
- subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
- description: 'Access data on Google Drive',
- defaults: {
- name: 'Google Drive',
- },
- inputs: ['main'],
- outputs: ['main'],
- credentials: [
- {
- name: 'googleApi',
- required: true,
- displayOptions: {
- show: {
- authentication: ['serviceAccount'],
- },
- },
- },
- {
- name: 'googleDriveOAuth2Api',
- required: true,
- displayOptions: {
- show: {
- authentication: ['oAuth2'],
- },
- },
- },
- ],
- properties: [
- {
- displayName: 'Authentication',
- name: 'authentication',
- type: 'options',
- options: [
- {
- name: 'Service Account',
- value: 'serviceAccount',
- },
- {
- name: 'OAuth2',
- value: 'oAuth2',
- },
- ],
- default: 'serviceAccount',
- displayOptions: {
- show: {
- '@version': [1],
- },
- },
- },
- {
- displayName: 'Authentication',
- name: 'authentication',
- type: 'options',
- options: [
- {
- // eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
- name: 'OAuth2 (recommended)',
- value: 'oAuth2',
- },
- {
- name: 'Service Account',
- value: 'serviceAccount',
- },
- ],
- default: 'oAuth2',
- displayOptions: {
- show: {
- '@version': [2],
- },
- },
- },
- {
- displayName: 'Resource',
- name: 'resource',
- type: 'options',
- noDataExpression: true,
- options: [
- {
- name: 'Drive',
- value: 'drive',
- },
- {
- name: 'File',
- value: 'file',
- },
- {
- name: 'Folder',
- value: 'folder',
- },
- ],
- default: 'file',
- },
-
- // ----------------------------------
- // operations
- // ----------------------------------
- {
- displayName: 'Operation',
- name: 'operation',
- type: 'options',
- noDataExpression: true,
- displayOptions: {
- show: {
- resource: ['file'],
- },
- },
- options: [
- {
- name: 'Copy',
- value: 'copy',
- description: 'Copy a file',
- action: 'Copy a file',
- },
- {
- name: 'Delete',
- value: 'delete',
- description: 'Delete a file',
- action: 'Delete a file',
- },
- {
- name: 'Download',
- value: 'download',
- description: 'Download a file',
- action: 'Download a file',
- },
- {
- name: 'List',
- value: 'list',
- description: 'List files and folders',
- action: 'List a file',
- },
- {
- name: 'Share',
- value: 'share',
- description: 'Share a file',
- action: 'Share a file',
- },
- {
- name: 'Update',
- value: 'update',
- description: 'Update a file',
- action: 'Update a file',
- },
- {
- name: 'Upload',
- value: 'upload',
- description: 'Upload a file',
- action: 'Upload a file',
- },
- ],
- default: 'upload',
- },
-
- {
- displayName: 'Operation',
- name: 'operation',
- type: 'options',
- noDataExpression: true,
- displayOptions: {
- show: {
- resource: ['folder'],
- },
- },
- options: [
- {
- name: 'Create',
- value: 'create',
- description: 'Create a folder',
- action: 'Create a folder',
- },
- {
- name: 'Delete',
- value: 'delete',
- description: 'Delete a folder',
- action: 'Delete a folder',
- },
- {
- name: 'Share',
- value: 'share',
- description: 'Share a folder',
- action: 'Share a folder',
- },
- ],
- default: 'create',
- },
-
- // ----------------------------------
- // file
- // ----------------------------------
-
- {
- displayName: 'File',
- name: 'fileId',
- type: 'resourceLocator',
- default: { mode: 'list', value: '' },
- required: true,
- modes: [
- {
- displayName: 'File',
- name: 'list',
- type: 'list',
- placeholder: 'Select a file...',
- typeOptions: {
- searchListMethod: 'fileSearch',
- searchable: true,
- },
- },
- {
- displayName: 'Link',
- name: 'url',
- type: 'string',
- placeholder:
- 'https://drive.google.com/file/d/1anGBg0b5re2VtF2bKu201_a-Vnz5BHq9Y4r-yBDAj5A/edit',
- extractValue: {
- type: 'regex',
- regex:
- 'https:\\/\\/(?:drive|docs)\\.google\\.com(?:\\/.*|)\\/d\\/([0-9a-zA-Z\\-_]+)(?:\\/.*|)',
- },
- validation: [
- {
- type: 'regex',
- properties: {
- regex:
- 'https:\\/\\/(?:drive|docs)\\.google.com(?:\\/.*|)\\/d\\/([0-9a-zA-Z\\-_]+)(?:\\/.*|)',
- errorMessage: 'Not a valid Google Drive File URL',
- },
- },
- ],
- },
- {
- displayName: 'ID',
- name: 'id',
- type: 'string',
- placeholder: '1anGBg0b5re2VtF2bKu201_a-Vnz5BHq9Y4r-yBDAj5A',
- validation: [
- {
- type: 'regex',
- properties: {
- regex: '[a-zA-Z0-9\\-_]{2,}',
- errorMessage: 'Not a valid Google Drive File ID',
- },
- },
- ],
- url: '=https://drive.google.com/file/d/{{$value}}/view',
- },
- ],
- displayOptions: {
- show: {
- operation: ['download', 'copy', 'update', 'delete', 'share'],
- resource: ['file'],
- },
- },
- description: 'The ID of the file',
- },
-
- {
- displayName: 'Folder',
- name: 'fileId',
- type: 'resourceLocator',
- default: { mode: 'list', value: '' },
- required: true,
- modes: [
- {
- displayName: 'Folder',
- name: 'list',
- type: 'list',
- placeholder: 'Select a folder...',
- typeOptions: {
- searchListMethod: 'folderSearch',
- searchable: true,
- },
- },
- {
- displayName: 'Link',
- name: 'url',
- type: 'string',
- placeholder: 'https://drive.google.com/drive/folders/1Tx9WHbA3wBpPB4C_HcoZDH9WZFWYxAMU',
- extractValue: {
- type: 'regex',
- regex:
- 'https:\\/\\/drive\\.google\\.com(?:\\/.*|)\\/folders\\/([0-9a-zA-Z\\-_]+)(?:\\/.*|)',
- },
- validation: [
- {
- type: 'regex',
- properties: {
- regex:
- 'https:\\/\\/drive\\.google\\.com(?:\\/.*|)\\/folders\\/([0-9a-zA-Z\\-_]+)(?:\\/.*|)',
- errorMessage: 'Not a valid Google Drive Folder URL',
- },
- },
- ],
- },
- {
- displayName: 'ID',
- name: 'id',
- type: 'string',
- placeholder: '1anGBg0b5re2VtF2bKu201_a-Vnz5BHq9Y4r-yBDAj5A',
- validation: [
- {
- type: 'regex',
- properties: {
- regex: '[a-zA-Z0-9\\-_]{2,}',
- errorMessage: 'Not a valid Google Drive Folder ID',
- },
- },
- ],
- url: '=https://drive.google.com/drive/folders/{{$value}}',
- },
- ],
- displayOptions: {
- show: {
- operation: ['delete', 'share'],
- resource: ['folder'],
- },
- },
- description: 'The ID of the folder',
- },
-
- // ----------------------------------
- // file:copy
- // ----------------------------------
-
- // ----------------------------------
- // file/folder:delete
- // ----------------------------------
-
- // ----------------------------------
- // file:download
- // ----------------------------------
- {
- displayName: 'Binary Property',
- name: 'binaryPropertyName',
- type: 'string',
- required: true,
- default: 'data',
- displayOptions: {
- show: {
- operation: ['download'],
- resource: ['file'],
- },
- },
- description: 'Name of the binary property to which to write the data of the read file',
- },
- {
- displayName: 'Options',
- name: 'options',
- type: 'collection',
- placeholder: 'Add Option',
- default: {},
- displayOptions: {
- show: {
- operation: ['download'],
- resource: ['file'],
- },
- },
- options: [
- {
- displayName: 'Google File Conversion',
- name: 'googleFileConversion',
- type: 'fixedCollection',
- typeOptions: {
- multipleValues: false,
- },
- default: {},
- placeholder: 'Add Conversion',
- options: [
- {
- displayName: 'Conversion',
- name: 'conversion',
- values: [
- {
- displayName: 'Google Docs',
- name: 'docsToFormat',
- type: 'options',
- options: [
- {
- name: 'To HTML',
- value: 'text/html',
- },
- {
- name: 'To MS Word',
- value:
- 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
- },
- {
- name: 'To OpenOffice Doc',
- value: 'application/vnd.oasis.opendocument.text',
- },
- {
- name: 'To PDF',
- value: 'application/pdf',
- },
- {
- name: 'To Rich Text',
- value: 'application/rtf',
- },
- ],
- default:
- 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
- description: 'Format used to export when downloading Google Docs files',
- },
- {
- displayName: 'Google Drawings',
- name: 'drawingsToFormat',
- type: 'options',
- options: [
- {
- name: 'To JPEG',
- value: 'image/jpeg',
- },
- {
- name: 'To PNG',
- value: 'image/png',
- },
- {
- name: 'To SVG',
- value: 'image/svg+xml',
- },
- {
- name: 'To PDF',
- value: 'application/pdf',
- },
- ],
- default: 'image/jpeg',
- description: 'Format used to export when downloading Google Drawings files',
- },
- {
- displayName: 'Google Slides',
- name: 'slidesToFormat',
- type: 'options',
- options: [
- {
- name: 'To MS PowerPoint',
- value:
- 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
- },
- {
- name: 'To PDF',
- value: 'application/pdf',
- },
- {
- name: 'To OpenOffice Presentation',
- value: 'application/vnd.oasis.opendocument.presentation',
- },
- {
- name: 'To Plain Text',
- value: 'text/plain',
- },
- ],
- default:
- 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
- description: 'Format used to export when downloading Google Slides files',
- },
- {
- displayName: 'Google Sheets',
- name: 'sheetsToFormat',
- type: 'options',
- options: [
- {
- name: 'To MS Excel',
- value: 'application/x-vnd.oasis.opendocument.spreadsheet',
- },
- {
- name: 'To PDF',
- value: 'application/pdf',
- },
- {
- name: 'To CSV',
- value: 'text/csv',
- },
- ],
- default: 'application/x-vnd.oasis.opendocument.spreadsheet',
- description: 'Format used to export when downloading Google Spreadsheets files',
- },
- ],
- },
- ],
- },
- {
- displayName: 'File Name',
- name: 'fileName',
- type: 'string',
- default: '',
- description: 'File name. Ex: data.pdf.',
- },
- ],
- },
-
- // ----------------------------------
- // file:list
- // ----------------------------------
- {
- displayName: 'Use Query String',
- name: 'useQueryString',
- type: 'boolean',
- default: false,
- displayOptions: {
- show: {
- operation: ['list'],
- resource: ['file'],
- },
- },
- description: 'Whether a query string should be used to filter results',
- },
- {
- displayName: 'Query String',
- name: 'queryString',
- type: 'string',
- default: '',
- displayOptions: {
- show: {
- operation: ['list'],
- useQueryString: [true],
- resource: ['file'],
- },
- },
- placeholder: "name contains 'invoice'",
- description: 'Query to use to return only specific files',
- },
- {
- displayName: 'Limit',
- name: 'limit',
- type: 'number',
- displayOptions: {
- show: {
- operation: ['list'],
- resource: ['file'],
- },
- },
- typeOptions: {
- minValue: 1,
- maxValue: 1000,
- },
- default: 50,
- description: 'Max number of results to return',
- },
- {
- displayName: 'Filters',
- name: 'queryFilters',
- placeholder: 'Add Filter',
- description: 'Filters to use to return only specific files',
- type: 'fixedCollection',
- typeOptions: {
- multipleValues: true,
- },
- default: {},
- displayOptions: {
- show: {
- operation: ['list'],
- useQueryString: [false],
- resource: ['file'],
- },
- },
- options: [
- {
- name: 'name',
- displayName: 'Name',
- values: [
- {
- displayName: 'Operation',
- name: 'operation',
- type: 'options',
- noDataExpression: true,
- options: [
- {
- name: 'Contains',
- value: 'contains',
- },
- {
- name: 'Is',
- value: 'is',
- },
- {
- name: 'Is Not',
- value: 'isNot',
- },
- ],
- default: 'contains',
- },
- {
- displayName: 'Value',
- name: 'value',
- type: 'string',
- default: '',
- description: 'The value for operation',
- },
- ],
- },
- {
- name: 'mimeType',
- displayName: 'Mime Type',
- values: [
- {
- displayName: 'Mime Type',
- name: 'mimeType',
- type: 'options',
- options: [
- {
- name: '3rd Party Shortcut',
- value: 'application/vnd.google-apps.drive-sdk',
- },
- {
- name: 'Audio',
- value: 'application/vnd.google-apps.audio',
- },
- {
- name: 'Custom Mime Type',
- value: 'custom',
- },
- {
- name: 'Google Apps Scripts',
- value: 'application/vnd.google-apps.script',
- },
- {
- name: 'Google Docs',
- value: 'application/vnd.google-apps.document',
- },
- {
- name: 'Google Drawing',
- value: 'application/vnd.google-apps.drawing',
- },
- {
- name: 'Google Drive File',
- value: 'application/vnd.google-apps.file',
- },
- {
- name: 'Google Drive Folder',
- value: 'application/vnd.google-apps.folder',
- },
- {
- name: 'Google Forms',
- value: 'application/vnd.google-apps.form',
- },
- {
- name: 'Google Fusion Tables',
- value: 'application/vnd.google-apps.fusiontable',
- },
- {
- name: 'Google My Maps',
- value: 'application/vnd.google-apps.map',
- },
- {
- name: 'Google Sheets',
- value: 'application/vnd.google-apps.spreadsheet',
- },
- {
- name: 'Google Sites',
- value: 'application/vnd.google-apps.site',
- },
- {
- name: 'Google Slides',
- value: 'application/vnd.google-apps.presentation',
- },
- {
- name: 'Photo',
- value: 'application/vnd.google-apps.photo',
- },
- {
- name: 'Unknown',
- value: 'application/vnd.google-apps.unknown',
- },
- {
- name: 'Video',
- value: 'application/vnd.google-apps.video',
- },
- ],
- default: 'application/vnd.google-apps.file',
- description: 'The Mime-Type of the files to return',
- },
- {
- displayName: 'Custom Mime Type',
- name: 'customMimeType',
- type: 'string',
- default: '',
- displayOptions: {
- show: {
- mimeType: ['custom'],
- },
- },
- },
- ],
- },
- ],
- },
-
- // ----------------------------------
- // file:share
- // ----------------------------------
- {
- displayName: 'Permissions',
- name: 'permissionsUi',
- placeholder: 'Add Permission',
- type: 'fixedCollection',
- default: {},
- typeOptions: {
- multipleValues: false,
- },
- displayOptions: {
- show: {
- resource: ['file', 'folder'],
- operation: ['share'],
- },
- },
- options: [
- {
- displayName: 'Permission',
- name: 'permissionsValues',
- values: [
- {
- displayName: 'Role',
- name: 'role',
- type: 'options',
- options: [
- {
- name: 'Commenter',
- value: 'commenter',
- },
- {
- name: 'File Organizer',
- value: 'fileOrganizer',
- },
- {
- name: 'Organizer',
- value: 'organizer',
- },
- {
- name: 'Owner',
- value: 'owner',
- },
- {
- name: 'Reader',
- value: 'reader',
- },
- {
- name: 'Writer',
- value: 'writer',
- },
- ],
- default: '',
- },
- {
- displayName: 'Type',
- name: 'type',
- type: 'options',
- options: [
- {
- name: 'User',
- value: 'user',
- },
- {
- name: 'Group',
- value: 'group',
- },
- {
- name: 'Domain',
- value: 'domain',
- },
- {
- name: 'Anyone',
- value: 'anyone',
- },
- ],
- default: '',
- description:
- 'Information about the different types can be found here',
- },
- {
- displayName: 'Email Address',
- name: 'emailAddress',
- type: 'string',
- displayOptions: {
- show: {
- type: ['user', 'group'],
- },
- },
- default: '',
- description:
- 'The email address of the user or group to which this permission refers',
- },
- {
- displayName: 'Domain',
- name: 'domain',
- type: 'string',
- displayOptions: {
- show: {
- type: ['domain'],
- },
- },
- default: '',
- description: 'The domain to which this permission refers',
- },
- {
- displayName: 'Allow File Discovery',
- name: 'allowFileDiscovery',
- type: 'boolean',
- displayOptions: {
- show: {
- type: ['domain', 'anyone'],
- },
- },
- default: false,
- description:
- 'Whether the permission allows the file to be discovered through search',
- },
- ],
- },
- ],
- },
-
- {
- displayName: 'Binary Data',
- name: 'binaryData',
- type: 'boolean',
- default: false,
- displayOptions: {
- show: {
- operation: ['upload'],
- resource: ['file'],
- },
- },
- description: 'Whether the data to upload should be taken from binary field',
- },
- {
- displayName: 'File Content',
- name: 'fileContent',
- type: 'string',
- default: '',
- displayOptions: {
- show: {
- operation: ['upload'],
- resource: ['file'],
- binaryData: [false],
- },
- },
- placeholder: '',
- description: 'The text content of the file to upload',
- },
- {
- displayName: 'Binary Property',
- name: 'binaryPropertyName',
- type: 'string',
- default: 'data',
- required: true,
- displayOptions: {
- show: {
- operation: ['upload'],
- resource: ['file'],
- binaryData: [true],
- },
- },
- placeholder: '',
- description:
- 'Name of the binary property which contains the data for the file to be uploaded',
- },
-
- // ----------------------------------
- // file:update
- // ----------------------------------
- {
- displayName: 'Update Fields',
- name: 'updateFields',
- type: 'collection',
- placeholder: 'Add Option',
- default: {},
- displayOptions: {
- show: {
- operation: ['update'],
- resource: ['file'],
- },
- },
- options: [
- {
- displayName: 'File Name',
- name: 'fileName',
- type: 'string',
- default: '',
- description: 'The name of the file',
- },
- {
- displayName: 'Keep Revision Forever',
- name: 'keepRevisionForever',
- type: 'boolean',
- default: false,
- description:
- "Whether to set the 'keepForever' field in the new head revision. This is only applicable to files with binary content in Google Drive. Only 200 revisions for the file can be kept forever. If the limit is reached, try deleting pinned revisions.",
- },
- {
- displayName: 'Move to Trash',
- name: 'trashed',
- type: 'boolean',
- default: false,
- description: 'Whether to move a file to the trash. Only the owner may trash a file.',
- },
- {
- displayName: 'OCR Language',
- name: 'ocrLanguage',
- type: 'string',
- default: '',
- description: 'A language hint for OCR processing during image import (ISO 639-1 code)',
- },
- {
- displayName: 'Parent ID',
- name: 'parentId',
- type: 'string',
- default: '',
- description: 'The ID of the parent to set',
- },
- {
- displayName: 'Use Content As Indexable Text',
- name: 'useContentAsIndexableText',
- type: 'boolean',
- default: false,
- description: 'Whether to use the uploaded content as indexable text',
- },
- ],
- },
- {
- displayName: 'Options',
- name: 'options',
- type: 'collection',
- placeholder: 'Add Option',
- default: {},
- displayOptions: {
- show: {
- operation: ['update'],
- resource: ['file'],
- },
- },
- options: [
- {
- displayName: 'Fields',
- name: 'fields',
- type: 'multiOptions',
- options: [
- {
- name: '[All]',
- value: '*',
- description: 'All fields',
- },
- {
- name: 'explicitlyTrashed',
- value: 'explicitlyTrashed',
- },
- {
- name: 'exportLinks',
- value: 'exportLinks',
- },
- {
- name: 'hasThumbnail',
- value: 'hasThumbnail',
- },
- {
- name: 'iconLink',
- value: 'iconLink',
- },
- {
- name: 'ID',
- value: 'id',
- },
- {
- name: 'Kind',
- value: 'kind',
- },
- {
- name: 'mimeType',
- value: 'mimeType',
- },
- {
- name: 'Name',
- value: 'name',
- },
- {
- name: 'Permissions',
- value: 'permissions',
- },
- {
- name: 'Shared',
- value: 'shared',
- },
- {
- name: 'Spaces',
- value: 'spaces',
- },
- {
- name: 'Starred',
- value: 'starred',
- },
- {
- name: 'thumbnailLink',
- value: 'thumbnailLink',
- },
- {
- name: 'Trashed',
- value: 'trashed',
- },
- {
- name: 'Version',
- value: 'version',
- },
- {
- name: 'webViewLink',
- value: 'webViewLink',
- },
- ],
- default: [],
- description: 'The fields to return',
- },
- ],
- },
- // ----------------------------------
- // file:upload
- // ----------------------------------
- {
- displayName: 'File Name',
- name: 'name',
- type: 'string',
- default: '',
- required: true,
- displayOptions: {
- show: {
- operation: ['upload'],
- resource: ['file'],
- },
- },
- placeholder: 'invoice_1.pdf',
- description: 'The name the file should be saved as',
- },
- // ----------------------------------
- {
- displayName: 'Resolve Data',
- name: 'resolveData',
- type: 'boolean',
- default: false,
- displayOptions: {
- show: {
- operation: ['upload'],
- resource: ['file'],
- },
- },
- // eslint-disable-next-line n8n-nodes-base/node-param-description-boolean-without-whether
- description:
- 'By default the response only contain the ID of the file. If this option gets activated, it will resolve the data automatically.',
- },
- {
- displayName: 'Parents',
- name: 'parents',
- type: 'string',
- typeOptions: {
- multipleValues: true,
- },
- default: [],
- displayOptions: {
- show: {
- operation: ['upload'],
- resource: ['file'],
- },
- },
- description: 'The IDs of the parent folders which contain the file',
- },
-
- // ----------------------------------
- // folder
- // ----------------------------------
-
- // ----------------------------------
- // folder:create
- // ----------------------------------
- {
- displayName: 'Folder',
- name: 'name',
- type: 'string',
- default: '',
- required: true,
- displayOptions: {
- show: {
- operation: ['create'],
- resource: ['folder'],
- },
- },
- placeholder: 'invoices',
- description: 'The name of folder to create',
- },
- {
- displayName: 'Options',
- name: 'options',
- type: 'collection',
- placeholder: 'Add Option',
- default: {},
- displayOptions: {
- show: {
- '/operation': ['copy', 'list', 'share', 'create'],
- '/resource': ['file', 'folder'],
- },
- },
- options: [
- {
- displayName: 'Email Message',
- name: 'emailMessage',
- type: 'string',
- displayOptions: {
- show: {
- '/operation': ['share'],
- '/resource': ['file', 'folder'],
- },
- },
- default: '',
- description: 'A plain text custom message to include in the notification email',
- },
- {
- displayName: 'Enforce Single Parent',
- name: 'enforceSingleParent',
- type: 'boolean',
- displayOptions: {
- show: {
- '/operation': ['share'],
- '/resource': ['file', 'folder'],
- },
- },
- default: false,
- description:
- 'Whether to opt in to API behavior that aims for all items to have exactly one parent. This parameter only takes effect if the item is not in a shared drive.',
- },
- {
- displayName: 'Fields',
- name: 'fields',
- type: 'multiOptions',
- displayOptions: {
- show: {
- '/operation': ['list', 'copy'],
- },
- },
- options: [
- {
- name: '*',
- value: '*',
- description: 'All fields',
- },
- {
- name: 'explicitlyTrashed',
- value: 'explicitlyTrashed',
- },
- {
- name: 'exportLinks',
- value: 'exportLinks',
- },
- {
- name: 'hasThumbnail',
- value: 'hasThumbnail',
- },
- {
- name: 'iconLink',
- value: 'iconLink',
- },
- {
- name: 'ID',
- value: 'id',
- },
- {
- name: 'Kind',
- value: 'kind',
- },
- {
- name: 'mimeType',
- value: 'mimeType',
- },
- {
- name: 'Name',
- value: 'name',
- },
- {
- name: 'Permissions',
- value: 'permissions',
- },
- {
- name: 'Shared',
- value: 'shared',
- },
- {
- name: 'Spaces',
- value: 'spaces',
- },
- {
- name: 'Starred',
- value: 'starred',
- },
- {
- name: 'thumbnailLink',
- value: 'thumbnailLink',
- },
- {
- name: 'Trashed',
- value: 'trashed',
- },
- {
- name: 'Version',
- value: 'version',
- },
- {
- name: 'webViewLink',
- value: 'webViewLink',
- },
- ],
- default: [],
- description: 'The fields to return',
- },
- {
- displayName: 'Move To New Owners Root',
- name: 'moveToNewOwnersRoot',
- type: 'boolean',
- displayOptions: {
- show: {
- '/operation': ['share'],
- '/resource': ['file', 'folder'],
- },
- },
- default: false,
- // eslint-disable-next-line n8n-nodes-base/node-param-description-boolean-without-whether
- description:
- "
This parameter only takes effect if the item is not in a shared drive and the request is attempting to transfer the ownership of the item.
When set to true, the item is moved to the new owner's My Drive root folder and all prior parents removed.
",
- },
- {
- displayName: 'Send Notification Email',
- name: 'sendNotificationEmail',
- type: 'boolean',
- displayOptions: {
- show: {
- '/operation': ['share'],
- '/resource': ['file', 'folder'],
- },
- },
- default: false,
- description: 'Whether to send a notification email when sharing to users or groups',
- },
- {
- displayName: 'Supports All Drives',
- name: 'supportsAllDrives',
- type: 'boolean',
- displayOptions: {
- show: {
- '/operation': ['share'],
- '/resource': ['file', 'folder'],
- },
- },
- default: false,
- description:
- 'Whether the requesting application supports both My Drives and shared drives',
- },
- {
- displayName: 'Transfer Ownership',
- name: 'transferOwnership',
- type: 'boolean',
- displayOptions: {
- show: {
- '/operation': ['share'],
- '/resource': ['file', 'folder'],
- },
- },
- default: false,
- description:
- 'Whether to transfer ownership to the specified user and downgrade the current owner to a writer',
- },
- {
- displayName: 'Use Domain Admin Access',
- name: 'useDomainAdminAccess',
- type: 'boolean',
- displayOptions: {
- show: {
- '/operation': ['share'],
- '/resource': ['file', 'folder'],
- },
- },
- default: false,
- description:
- 'Whether to perform the operation as domain administrator, i.e. if you are an administrator of the domain to which the shared drive belongs, you will be granted access automatically.',
- },
-
- {
- displayName: 'File Name',
- name: 'name',
- type: 'string',
- displayOptions: {
- show: {
- '/operation': ['copy'],
- '/resource': ['file'],
- },
- },
- default: '',
- placeholder: 'invoice_1.pdf',
- description: 'The name the file should be saved as',
- },
- {
- displayName: 'Parents',
- name: 'parents',
- type: 'string',
- displayOptions: {
- show: {
- '/operation': ['copy', 'create'],
- '/resource': ['file', 'folder'],
- },
- },
- typeOptions: {
- multipleValues: true,
- },
- default: [],
- description: 'The IDs of the parent folders the file/folder should be saved in',
- },
- {
- displayName: 'Spaces',
- name: 'spaces',
- type: 'multiOptions',
- displayOptions: {
- show: {
- '/operation': ['list'],
- '/resource': ['file'],
- },
- },
- options: [
- {
- name: '[All]',
- value: '*',
- description: 'All spaces',
- },
- {
- name: 'appDataFolder',
- value: 'appDataFolder',
- },
- {
- name: 'Drive',
- value: 'drive',
- },
- {
- name: 'Photos',
- value: 'photos',
- },
- ],
- default: [],
- description: 'The spaces to operate on',
- },
- {
- displayName: 'Corpora',
- name: 'corpora',
- type: 'options',
- displayOptions: {
- show: {
- '/operation': ['list'],
- '/resource': ['file'],
- },
- },
- options: [
- {
- name: 'User',
- value: 'user',
- description: 'All files in "My Drive" and "Shared with me"',
- },
- {
- name: 'Domain',
- value: 'domain',
- description: "All files shared to the user's domain that are searchable",
- },
- {
- name: 'Drive',
- value: 'drive',
- description: 'All files contained in a single shared drive',
- },
- {
- name: 'allDrives',
- value: 'allDrives',
- description: 'All drives',
- },
- ],
- default: '',
- description: 'The corpora to operate on',
- },
- {
- displayName: 'Drive ID',
- name: 'driveId',
- type: 'string',
- default: '',
- displayOptions: {
- show: {
- '/operation': ['list'],
- '/resource': ['file'],
- corpora: ['drive'],
- },
- },
- description:
- 'ID of the shared drive to search. The driveId parameter must be specified if and only if corpora is set to drive.',
- },
- ],
- },
- // ----------------------------------
- // drive
- // ----------------------------------
- {
- displayName: 'Operation',
- name: 'operation',
- type: 'options',
- noDataExpression: true,
- displayOptions: {
- show: {
- resource: ['drive'],
- },
- },
- options: [
- {
- name: 'Create',
- value: 'create',
- description: 'Create a drive',
- action: 'Create a drive',
- },
- {
- name: 'Delete',
- value: 'delete',
- description: 'Delete a drive',
- action: 'Delete a drive',
- },
- {
- name: 'Get',
- value: 'get',
- description: 'Get a drive',
- action: 'Get a drive',
- },
- {
- name: 'List',
- value: 'list',
- description: 'List all drives',
- action: 'List all drives',
- },
- {
- name: 'Update',
- value: 'update',
- description: 'Update a drive',
- action: 'Update a drive',
- },
- ],
- default: 'create',
- },
-
- {
- displayName: 'Drive',
- name: 'driveId',
- type: 'resourceLocator',
- default: { mode: 'list', value: '' },
- required: true,
- hint: 'The Google Drive drive to operate on',
- modes: [
- {
- displayName: 'Drive',
- name: 'list',
- type: 'list',
- placeholder: 'Drive',
- typeOptions: {
- searchListMethod: 'driveSearch',
- searchable: true,
- },
- },
- {
- displayName: 'Link',
- name: 'url',
- type: 'string',
- placeholder: 'https://drive.google.com/drive/folders/0AaaaaAAAAAAAaa',
- extractValue: {
- type: 'regex',
- regex:
- 'https:\\/\\/drive\\.google\\.com(?:\\/.*|)\\/folders\\/([0-9a-zA-Z\\-_]+)(?:\\/.*|)',
- },
- validation: [
- {
- type: 'regex',
- properties: {
- regex:
- 'https:\\/\\/drive\\.google\\.com(?:\\/.*|)\\/folders\\/([0-9a-zA-Z\\-_]+)(?:\\/.*|)',
- errorMessage: 'Not a valid Google Drive Drive URL',
- },
- },
- ],
- },
- {
- displayName: 'ID',
- name: 'id',
- type: 'string',
- hint: 'The ID of the shared drive',
- validation: [
- {
- type: 'regex',
- properties: {
- regex: '[a-zA-Z0-9\\-_]{2,}',
- errorMessage: 'Not a valid Google Drive Drive ID',
- },
- },
- ],
- url: '=https://drive.google.com/drive/folders/{{$value}}',
- },
- ],
- displayOptions: {
- show: {
- operation: ['delete', 'get', 'update'],
- resource: ['drive'],
- },
- },
- description: 'The ID of the drive',
- },
-
- // ----------------------------------
- // drive:create
- // ----------------------------------
- {
- displayName: 'Name',
- name: 'name',
- type: 'string',
- default: '',
- displayOptions: {
- show: {
- operation: ['create'],
- resource: ['drive'],
- },
- },
- description: 'The name of this shared drive',
- },
- {
- displayName: 'Options',
- name: 'options',
- type: 'collection',
- placeholder: 'Add Option',
- default: {},
- displayOptions: {
- show: {
- operation: ['create'],
- resource: ['drive'],
- },
- },
- options: [
- {
- displayName: 'Capabilities',
- name: 'capabilities',
- type: 'collection',
- placeholder: 'Add Field',
- default: {},
- options: [
- {
- displayName: 'Can Add Children',
- name: 'canAddChildren',
- type: 'boolean',
- default: false,
- description:
- 'Whether the current user can add children to folders in this shared drive',
- },
- {
- displayName: 'Can Change Copy Requires Writer Permission Restriction',
- name: 'canChangeCopyRequiresWriterPermissionRestriction',
- type: 'boolean',
- default: false,
- description:
- 'Whether the current user can change the copyRequiresWriterPermission restriction of this shared drive',
- },
- {
- displayName: 'Can Change Domain Users Only Restriction',
- name: 'canChangeDomainUsersOnlyRestriction',
- type: 'boolean',
- default: false,
- description:
- 'Whether the current user can change the domainUsersOnly restriction of this shared drive',
- },
- {
- displayName: 'Can Change Drive Background',
- name: 'canChangeDriveBackground',
- type: 'boolean',
- default: false,
- description:
- 'Whether the current user can change the background of this shared drive',
- },
- {
- displayName: 'Can Change Drive Members Only Restriction',
- name: 'canChangeDriveMembersOnlyRestriction',
- type: 'boolean',
- default: false,
- description:
- 'Whether the current user can change the driveMembersOnly restriction of this shared drive',
- },
- {
- displayName: 'Can Comment',
- name: 'canComment',
- type: 'boolean',
- default: false,
- description: 'Whether the current user can comment on files in this shared drive',
- },
- {
- displayName: 'Can Copy',
- name: 'canCopy',
- type: 'boolean',
- default: false,
- description: 'Whether the current user can copy files in this shared drive',
- },
- {
- displayName: 'Can Delete Children',
- name: 'canDeleteChildren',
- type: 'boolean',
- default: false,
- description:
- 'Whether the current user can delete children from folders in this shared drive',
- },
- {
- displayName: 'Can Delete Drive',
- name: 'canDeleteDrive',
- type: 'boolean',
- default: false,
- description:
- 'Whether the current user can delete this shared drive. Attempting to delete the shared drive may still fail if there are untrashed items inside the shared drive.',
- },
- {
- displayName: 'Can Download',
- name: 'canDownload',
- type: 'boolean',
- default: false,
- description: 'Whether the current user can download files in this shared drive',
- },
- {
- displayName: 'Can Edit',
- name: 'canEdit',
- type: 'boolean',
- default: false,
- description: 'Whether the current user can edit files in this shared drive',
- },
- {
- displayName: 'Can List Children',
- name: 'canListChildren',
- type: 'boolean',
- default: false,
- description:
- 'Whether the current user can list the children of folders in this shared drive',
- },
- {
- displayName: 'Can Manage Members',
- name: 'canManageMembers',
- type: 'boolean',
- default: false,
- description:
- 'Whether the current user can add members to this shared drive or remove them or change their role',
- },
- {
- displayName: 'Can Read Revisions',
- name: 'canReadRevisions',
- type: 'boolean',
- default: false,
- description:
- 'Whether the current user can read the revisions resource of files in this shared drive',
- },
- {
- displayName: 'Can Rename',
- name: 'canRename',
- type: 'boolean',
- default: false,
- description:
- 'Whether the current user can rename files or folders in this shared drive',
- },
- {
- displayName: 'Can Rename Drive',
- name: 'canRenameDrive',
- type: 'boolean',
- default: false,
- description: 'Whether the current user can rename this shared drive',
- },
- {
- displayName: 'Can Share',
- name: 'canShare',
- type: 'boolean',
- default: false,
- description: 'Whether the current user can rename this shared drive',
- },
- {
- displayName: 'Can Trash Children',
- name: 'canTrashChildren',
- type: 'boolean',
- default: false,
- description:
- 'Whether the current user can trash children from folders in this shared drive',
- },
- ],
- },
- {
- displayName: 'Color RGB',
- name: 'colorRgb',
- type: 'color',
- default: '',
- description: 'The color of this shared drive as an RGB hex string',
- },
- {
- displayName: 'Created Time',
- name: 'createdTime',
- type: 'dateTime',
- default: '',
- description: 'The time at which the shared drive was created (RFC 3339 date-time)',
- },
- {
- displayName: 'Hidden',
- name: 'hidden',
- type: 'boolean',
- default: false,
- description: 'Whether the shared drive is hidden from default view',
- },
- {
- displayName: 'Restrictions',
- name: 'restrictions',
- type: 'collection',
- placeholder: 'Add Field',
- default: {},
- options: [
- {
- displayName: 'Admin Managed Restrictions',
- name: 'adminManagedRestrictions',
- type: 'boolean',
- default: false,
- description:
- 'Whether the options to copy, print, or download files inside this shared drive, should be disabled for readers and commenters. When this restriction is set to true, it will override the similarly named field to true for any file inside this shared drive.',
- },
- {
- displayName: 'Copy Requires Writer Permission',
- name: 'copyRequiresWriterPermission',
- type: 'boolean',
- default: false,
- description:
- 'Whether the options to copy, print, or download files inside this shared drive, should be disabled for readers and commenters. When this restriction is set to true, it will override the similarly named field to true for any file inside this shared drive.',
- },
- {
- displayName: 'Domain Users Only',
- name: 'domainUsersOnly',
- type: 'boolean',
- default: false,
- description:
- 'Whether access to this shared drive and items inside this shared drive is restricted to users of the domain to which this shared drive belongs. This restriction may be overridden by other sharing policies controlled outside of this shared drive.',
- },
- {
- displayName: 'Drive Members Only',
- name: 'driveMembersOnly',
- type: 'boolean',
- default: false,
- description:
- 'Whether access to items inside this shared drive is restricted to its members',
- },
- ],
- },
- ],
- },
- // ----------------------------------
- // drive:delete
- // ----------------------------------
-
- // ----------------------------------
- // drive:get
- // ----------------------------------
- {
- displayName: 'Options',
- name: 'options',
- type: 'collection',
- placeholder: 'Add Option',
- default: {},
- displayOptions: {
- show: {
- operation: ['get'],
- resource: ['drive'],
- },
- },
- options: [
- {
- displayName: 'Use Domain Admin Access',
- name: 'useDomainAdminAccess',
- type: 'boolean',
- default: false,
- description:
- 'Whether to issue the request as a domain administrator; if set to true, then the requester will be granted access if they are an administrator of the domain to which the shared drive belongs. (Default: false).',
- },
- ],
- },
- // ----------------------------------
- // drive:list
- // ----------------------------------
- {
- displayName: 'Return All',
- name: 'returnAll',
- type: 'boolean',
- displayOptions: {
- show: {
- operation: ['list'],
- resource: ['drive'],
- },
- },
- default: false,
- description: 'Whether to return all results or only up to a given limit',
- },
- {
- displayName: 'Limit',
- name: 'limit',
- type: 'number',
- displayOptions: {
- show: {
- operation: ['list'],
- resource: ['drive'],
- returnAll: [false],
- },
- },
- typeOptions: {
- minValue: 1,
- maxValue: 200,
- },
- default: 100,
- description: 'Max number of results to return',
- },
- {
- displayName: 'Options',
- name: 'options',
- type: 'collection',
- placeholder: 'Add Option',
- default: {},
- displayOptions: {
- show: {
- operation: ['list'],
- resource: ['drive'],
- },
- },
- options: [
- {
- displayName: 'Query',
- name: 'q',
- type: 'string',
- default: '',
- description:
- 'Query string for searching shared drives. See the "Search for shared drives" guide for supported syntax.',
- },
- {
- displayName: 'Use Domain Admin Access',
- name: 'useDomainAdminAccess',
- type: 'boolean',
- default: false,
- description:
- 'Whether to issue the request as a domain administrator; if set to true, then the requester will be granted access if they are an administrator of the domain to which the shared drive belongs. (Default: false).',
- },
- ],
- },
- // ----------------------------------
- // drive:update
- // ----------------------------------
- {
- displayName: 'Update Fields',
- name: 'options',
- type: 'collection',
- placeholder: 'Add Option',
- default: {},
- displayOptions: {
- show: {
- operation: ['update'],
- resource: ['drive'],
- },
- },
- options: [
- {
- displayName: 'Color RGB',
- name: 'colorRgb',
- type: 'color',
- default: '',
- description: 'The color of this shared drive as an RGB hex string',
- },
- {
- displayName: 'Name',
- name: 'name',
- type: 'string',
- default: '',
- description: 'The name of this shared drive',
- },
- {
- displayName: 'Restrictions',
- name: 'restrictions',
- type: 'collection',
- placeholder: 'Add Field',
- default: {},
- options: [
- {
- displayName: 'Admin Managed Restrictions',
- name: 'adminManagedRestrictions',
- type: 'boolean',
- default: false,
- description:
- 'Whether the options to copy, print, or download files inside this shared drive, should be disabled for readers and commenters. When this restriction is set to true, it will override the similarly named field to true for any file inside this shared drive.',
- },
- {
- displayName: 'Copy Requires Writer Permission',
- name: 'copyRequiresWriterPermission',
- type: 'boolean',
- default: false,
- description:
- 'Whether the options to copy, print, or download files inside this shared drive, should be disabled for readers and commenters. When this restriction is set to true, it will override the similarly named field to true for any file inside this shared drive.',
- },
- {
- displayName: 'Domain Users Only',
- name: 'domainUsersOnly',
- type: 'boolean',
- default: false,
- description:
- 'Whether access to this shared drive and items inside this shared drive is restricted to users of the domain to which this shared drive belongs. This restriction may be overridden by other sharing policies controlled outside of this shared drive.',
- },
- {
- displayName: 'Drive Members Only',
- name: 'driveMembersOnly',
- type: 'boolean',
- default: false,
- description:
- 'Whether access to items inside this shared drive is restricted to its members',
- },
- ],
- },
- ],
- },
- {
- displayName: 'Options',
- name: 'options',
- type: 'collection',
- placeholder: 'Add Option',
- default: {},
- displayOptions: {
- show: {
- operation: ['upload'],
- resource: ['file'],
- },
- },
- options: [
- {
- displayName: 'APP Properties',
- name: 'appPropertiesUi',
- placeholder: 'Add Property',
- type: 'fixedCollection',
- default: {},
- typeOptions: {
- multipleValues: true,
- },
- description:
- 'A collection of arbitrary key-value pairs which are private to the requesting app',
- options: [
- {
- name: 'appPropertyValues',
- displayName: 'APP Property',
- values: [
- {
- displayName: 'Key',
- name: 'key',
- type: 'string',
- default: '',
- description: 'Name of the key to add',
- },
- {
- displayName: 'Value',
- name: 'value',
- type: 'string',
- default: '',
- description: 'Value to set for the key',
- },
- ],
- },
- ],
- },
- {
- displayName: 'Properties',
- name: 'propertiesUi',
- placeholder: 'Add Property',
- type: 'fixedCollection',
- default: {},
- typeOptions: {
- multipleValues: true,
- },
- description: 'A collection of arbitrary key-value pairs which are visible to all apps',
- options: [
- {
- name: 'propertyValues',
- displayName: 'Property',
- values: [
- {
- displayName: 'Key',
- name: 'key',
- type: 'string',
- default: '',
- description: 'Name of the key to add',
- },
- {
- displayName: 'Value',
- name: 'value',
- type: 'string',
- default: '',
- description: 'Value to set for the key',
- },
- ],
- },
- ],
- },
- ],
- },
- ],
- };
-
- methods = {
- listSearch: {
- fileSearch,
- folderSearch,
- driveSearch,
- },
- };
-
- async execute(this: IExecuteFunctions): Promise {
- const items = this.getInputData();
- const returnData: INodeExecutionData[] = [];
-
- const resource = this.getNodeParameter('resource', 0);
- const operation = this.getNodeParameter('operation', 0);
-
- for (let i = 0; i < items.length; i++) {
- try {
- const options = this.getNodeParameter('options', i, {});
-
- let queryFields = 'id, name';
- if (options?.fields) {
- const fields = options.fields as string[];
- if (fields.includes('*')) {
- queryFields = '*';
- } else {
- queryFields = fields.join(', ');
- }
- }
-
- if (resource === 'drive') {
- if (operation === 'create') {
- // ----------------------------------
- // create
- // ----------------------------------
-
- const name = this.getNodeParameter('name', i) as string;
-
- const body: IDataObject = {
- name,
- };
-
- Object.assign(body, options);
-
- const response = await googleApiRequest.call(this, 'POST', '/drive/v3/drives', body, {
- requestId: uuid(),
- });
-
- const executionData = this.helpers.constructExecutionMetaData(
- this.helpers.returnJsonArray(response as IDataObject[]),
- { itemData: { item: i } },
- );
-
- returnData.push(...executionData);
- }
- if (operation === 'delete') {
- // ----------------------------------
- // delete
- // ----------------------------------
-
- const driveId = this.getNodeParameter('driveId', i, undefined, {
- extractValue: true,
- }) as string;
-
- await googleApiRequest.call(this, 'DELETE', `/drive/v3/drives/${driveId}`);
-
- const executionData = this.helpers.constructExecutionMetaData(
- this.helpers.returnJsonArray({ success: true }),
- { itemData: { item: i } },
- );
-
- returnData.push(...executionData);
- }
- if (operation === 'get') {
- // ----------------------------------
- // get
- // ----------------------------------
-
- const driveId = this.getNodeParameter('driveId', i, undefined, {
- extractValue: true,
- }) as string;
-
- const qs: IDataObject = {};
-
- Object.assign(qs, options);
-
- const response = await googleApiRequest.call(
- this,
- 'GET',
- `/drive/v3/drives/${driveId}`,
- {},
- qs,
- );
-
- const executionData = this.helpers.constructExecutionMetaData(
- this.helpers.returnJsonArray(response as IDataObject[]),
- { itemData: { item: i } },
- );
-
- returnData.push(...executionData);
- }
- if (operation === 'list') {
- // ----------------------------------
- // list
- // ----------------------------------
- const returnAll = this.getNodeParameter('returnAll', i);
-
- const qs: IDataObject = {};
-
- let response: IDataObject[] = [];
-
- Object.assign(qs, options);
-
- if (returnAll) {
- response = await googleApiRequestAllItems.call(
- this,
- 'drives',
- 'GET',
- '/drive/v3/drives',
- {},
- qs,
- );
- } else {
- qs.pageSize = this.getNodeParameter('limit', i);
- const data = await googleApiRequest.call(this, 'GET', '/drive/v3/drives', {}, qs);
- response = data.drives as IDataObject[];
- }
-
- const executionData = this.helpers.constructExecutionMetaData(
- this.helpers.returnJsonArray(response),
- { itemData: { item: i } },
- );
-
- returnData.push(...executionData);
- }
- if (operation === 'update') {
- // ----------------------------------
- // update
- // ----------------------------------
-
- const driveId = this.getNodeParameter('driveId', i, undefined, {
- extractValue: true,
- }) as string;
-
- const body: IDataObject = {};
-
- Object.assign(body, options);
-
- const response = await googleApiRequest.call(
- this,
- 'PATCH',
- `/drive/v3/drives/${driveId}`,
- body,
- );
-
- const executionData = this.helpers.constructExecutionMetaData(
- this.helpers.returnJsonArray(response as IDataObject[]),
- { itemData: { item: i } },
- );
-
- returnData.push(...executionData);
- }
- }
- if (resource === 'file') {
- if (operation === 'copy') {
- // ----------------------------------
- // copy
- // ----------------------------------
-
- const fileId = this.getNodeParameter('fileId', i, undefined, {
- extractValue: true,
- }) as string;
-
- const body: IDataObject = {
- fields: queryFields,
- };
-
- const optionProperties = ['name', 'parents'];
- for (const propertyName of optionProperties) {
- if (options[propertyName] !== undefined) {
- body[propertyName] = options[propertyName];
- }
- }
-
- const qs = {
- supportsAllDrives: true,
- };
-
- const response = await googleApiRequest.call(
- this,
- 'POST',
- `/drive/v3/files/${fileId}/copy`,
- body,
- qs,
- );
-
- const executionData = this.helpers.constructExecutionMetaData(
- this.helpers.returnJsonArray(response as IDataObject[]),
- { itemData: { item: i } },
- );
-
- returnData.push(...executionData);
- } else if (operation === 'download') {
- // ----------------------------------
- // download
- // ----------------------------------
-
- const fileId = this.getNodeParameter('fileId', i, undefined, {
- extractValue: true,
- }) as string;
- const downloadOptions = this.getNodeParameter('options', i);
-
- const requestOptions = {
- useStream: true,
- resolveWithFullResponse: true,
- encoding: null,
- json: false,
- };
-
- const file = await googleApiRequest.call(
- this,
- 'GET',
- `/drive/v3/files/${fileId}`,
- {},
- { fields: 'mimeType,name', supportsTeamDrives: true },
- );
- let response;
-
- if (file.mimeType.includes('vnd.google-apps')) {
- const parameterKey = 'options.googleFileConversion.conversion';
- const type = file.mimeType.split('.')[2];
- let mime;
- if (type === 'document') {
- mime = this.getNodeParameter(
- `${parameterKey}.docsToFormat`,
- i,
- 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
- ) as string;
- } else if (type === 'presentation') {
- mime = this.getNodeParameter(
- `${parameterKey}.slidesToFormat`,
- i,
- 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
- ) as string;
- } else if (type === 'spreadsheet') {
- mime = this.getNodeParameter(
- `${parameterKey}.sheetsToFormat`,
- i,
- 'application/x-vnd.oasis.opendocument.spreadsheet',
- ) as string;
- } else {
- mime = this.getNodeParameter(
- `${parameterKey}.drawingsToFormat`,
- i,
- 'image/jpeg',
- ) as string;
- }
- response = await googleApiRequest.call(
- this,
- 'GET',
- `/drive/v3/files/${fileId}/export`,
- {},
- { mimeType: mime },
- undefined,
- requestOptions,
- );
- } else {
- response = await googleApiRequest.call(
- this,
- 'GET',
- `/drive/v3/files/${fileId}`,
- {},
- { alt: 'media' },
- undefined,
- requestOptions,
- );
- }
-
- const mimeType = response.headers['content-type'] ?? file.mimeType ?? undefined;
- const fileName = downloadOptions.fileName ?? file.name ?? undefined;
-
- const newItem: INodeExecutionData = {
- json: items[i].json,
- binary: {},
- };
-
- if (items[i].binary !== undefined) {
- // Create a shallow copy of the binary data so that the old
- // data references which do not get changed still stay behind
- // but the incoming data does not get changed.
- // @ts-ignore
- Object.assign(newItem.binary, items[i].binary);
- }
-
- items[i] = newItem;
-
- const dataPropertyNameDownload = this.getNodeParameter('binaryPropertyName', i);
-
- items[i].binary![dataPropertyNameDownload] = await this.helpers.prepareBinaryData(
- response.body as unknown as Readable,
- fileName as string,
- mimeType as string,
- );
- } else if (operation === 'list') {
- // ----------------------------------
- // list
- // ----------------------------------
-
- let querySpaces = '';
- if (options.spaces) {
- const spaces = options.spaces as string[];
- if (spaces.includes('*')) {
- querySpaces = 'appDataFolder, drive, photos';
- } else {
- querySpaces = spaces.join(', ');
- }
- }
-
- let queryCorpora = '';
- if (options.corpora) {
- queryCorpora = options.corpora as string;
- }
-
- let driveId: string | undefined;
- driveId = options.driveId as string;
- if (driveId === '') {
- driveId = undefined;
- }
-
- let queryString = '';
- const useQueryString = this.getNodeParameter('useQueryString', i) as boolean;
- if (useQueryString) {
- // Use the user defined query string
- queryString = this.getNodeParameter('queryString', i) as string;
- } else {
- // Build query string out of parameters set by user
- const queryFilters = this.getNodeParameter('queryFilters', i) as IDataObject;
-
- const queryFilterFields: string[] = [];
- if (queryFilters.name) {
- (queryFilters.name as IDataObject[]).forEach((nameFilter) => {
- let filterOperation = nameFilter.operation;
- if (filterOperation === 'is') {
- filterOperation = '=';
- } else if (filterOperation === 'isNot') {
- filterOperation = '!=';
- }
- queryFilterFields.push(`name ${filterOperation} '${nameFilter.value}'`);
- });
-
- queryString += queryFilterFields.join(' or ');
- }
-
- queryFilterFields.length = 0;
- if (queryFilters.mimeType) {
- (queryFilters.mimeType as IDataObject[]).forEach((mimeTypeFilter) => {
- let mimeType = mimeTypeFilter.mimeType;
- if (mimeTypeFilter.mimeType === 'custom') {
- mimeType = mimeTypeFilter.customMimeType;
- }
- queryFilterFields.push(`mimeType = '${mimeType}'`);
- });
-
- if (queryFilterFields.length) {
- if (queryString !== '') {
- queryString += ' and ';
- }
-
- queryString += queryFilterFields.join(' or ');
- }
- }
- }
-
- const pageSize = this.getNodeParameter('limit', i);
-
- const qs = {
- pageSize,
- orderBy: 'modifiedTime',
- fields: `nextPageToken, files(${queryFields})`,
- spaces: querySpaces,
- q: queryString,
- includeItemsFromAllDrives: queryCorpora !== '' || driveId !== '',
- supportsAllDrives: queryCorpora !== '' || driveId !== '',
- };
-
- const response = await googleApiRequest.call(this, 'GET', '/drive/v3/files', {}, qs);
-
- const files = response.files;
-
- const version = this.getNode().typeVersion;
-
- const executionData = this.helpers.constructExecutionMetaData(
- this.helpers.returnJsonArray(files as IDataObject[]),
- { itemData: { item: i } },
- );
-
- if (version === 1) {
- return [executionData];
- }
-
- returnData.push(...executionData);
- } else if (operation === 'upload') {
- // ----------------------------------
- // upload
- // ----------------------------------
- const resolveData = this.getNodeParameter('resolveData', 0);
-
- let contentLength: number;
- let fileContent: Buffer | Readable;
- let originalFilename: string | undefined;
- let mimeType = 'text/plain';
-
- if (this.getNodeParameter('binaryData', i)) {
- const binaryPropertyName = this.getNodeParameter('binaryPropertyName', i);
- const binaryData = this.helpers.assertBinaryData(i, binaryPropertyName);
- if (binaryData.id) {
- // Stream data in 256KB chunks, and upload the via the resumable upload api
- fileContent = this.helpers.getBinaryStream(binaryData.id, UPLOAD_CHUNK_SIZE);
- const metadata = await this.helpers.getBinaryMetadata(binaryData.id);
- contentLength = metadata.fileSize;
- originalFilename = metadata.fileName;
- if (metadata.mimeType) mimeType = binaryData.mimeType;
- } else {
- fileContent = Buffer.from(binaryData.data, BINARY_ENCODING);
- contentLength = fileContent.length;
- originalFilename = binaryData.fileName;
- mimeType = binaryData.mimeType;
- }
- } else {
- // Is text file
- fileContent = Buffer.from(this.getNodeParameter('fileContent', i) as string, 'utf8');
- contentLength = fileContent.byteLength;
- }
-
- const name = this.getNodeParameter('name', i) as string;
- const parents = this.getNodeParameter('parents', i) as string[];
-
- let uploadId;
- if (Buffer.isBuffer(fileContent)) {
- const response = await googleApiRequest.call(
- this,
- 'POST',
- '/upload/drive/v3/files',
- fileContent,
- {
- fields: queryFields,
- uploadType: 'media',
- },
- undefined,
- {
- headers: {
- 'Content-Type': mimeType,
- 'Content-Length': contentLength,
- },
- encoding: null,
- json: false,
- },
- );
- uploadId = JSON.parse(response as string).id;
- } else {
- const resumableUpload = await googleApiRequest.call(
- this,
- 'POST',
- '/upload/drive/v3/files',
- undefined,
- { uploadType: 'resumable' },
- undefined,
- {
- resolveWithFullResponse: true,
- },
- );
- const uploadUrl = resumableUpload.headers.location;
-
- let offset = 0;
- for await (const chunk of fileContent) {
- const nextOffset = offset + Number(chunk.length);
- try {
- const response = await this.helpers.httpRequest({
- method: 'PUT',
- url: uploadUrl,
- headers: {
- 'Content-Length': chunk.length,
- 'Content-Range': `bytes ${offset}-${nextOffset - 1}/${contentLength}`,
- },
- body: chunk,
- });
- uploadId = response.id;
- } catch (error) {
- if (error.response?.status !== 308) throw error;
- }
- offset = nextOffset;
- }
- }
-
- const requestBody = {
- mimeType,
- name,
- originalFilename,
- };
-
- const properties = this.getNodeParameter(
- 'options.propertiesUi.propertyValues',
- i,
- [],
- ) as IDataObject[];
-
- if (properties.length) {
- Object.assign(requestBody, {
- properties: properties.reduce(
- (obj, value) => Object.assign(obj, { [`${value.key}`]: value.value }),
- {},
- ),
- });
- }
-
- const appProperties = this.getNodeParameter(
- 'options.appPropertiesUi.appPropertyValues',
- i,
- [],
- ) as IDataObject[];
-
- if (properties.length) {
- Object.assign(requestBody, {
- appProperties: appProperties.reduce(
- (obj, value) => Object.assign(obj, { [`${value.key}`]: value.value }),
- {},
- ),
- });
- }
-
- let response = await googleApiRequest.call(
- this,
- 'PATCH',
- `/drive/v3/files/${uploadId}`,
- requestBody,
- {
- addParents: parents.join(','),
- // When set to true shared drives can be used.
- supportsAllDrives: true,
- },
- );
-
- if (resolveData) {
- response = await googleApiRequest.call(
- this,
- 'GET',
- `/drive/v3/files/${response.id}`,
- {},
- { fields: '*' },
- );
- }
-
- const executionData = this.helpers.constructExecutionMetaData(
- this.helpers.returnJsonArray(response as IDataObject[]),
- { itemData: { item: i } },
- );
- returnData.push(...executionData);
- } else if (operation === 'update') {
- // ----------------------------------
- // file:update
- // ----------------------------------
-
- const id = this.getNodeParameter('fileId', i, undefined, {
- extractValue: true,
- }) as string;
- const updateFields = this.getNodeParameter('updateFields', i, {});
-
- const qs: IDataObject = {
- supportsAllDrives: true,
- };
-
- Object.assign(qs, options);
-
- qs.fields = queryFields;
-
- const body: IDataObject = {};
-
- if (updateFields.fileName) {
- body.name = updateFields.fileName;
- }
-
- if (updateFields.hasOwnProperty('trashed')) {
- body.trashed = updateFields.trashed;
- }
-
- if (updateFields.parentId && updateFields.parentId !== '') {
- qs.addParents = updateFields.parentId;
- }
-
- const responseData = await googleApiRequest.call(
- this,
- 'PATCH',
- `/drive/v3/files/${id}`,
- body,
- qs,
- );
-
- const executionData = this.helpers.constructExecutionMetaData(
- this.helpers.returnJsonArray(responseData as IDataObject[]),
- { itemData: { item: i } },
- );
- returnData.push(...executionData);
- }
- }
- if (resource === 'folder') {
- if (operation === 'create') {
- // ----------------------------------
- // folder:create
- // ----------------------------------
-
- const name = this.getNodeParameter('name', i) as string;
-
- const body = {
- name,
- mimeType: 'application/vnd.google-apps.folder',
- parents: options.parents || [],
- };
-
- const qs = {
- fields: queryFields,
- supportsAllDrives: true,
- };
-
- const response = await googleApiRequest.call(this, 'POST', '/drive/v3/files', body, qs);
-
- const executionData = this.helpers.constructExecutionMetaData(
- this.helpers.returnJsonArray(response as IDataObject[]),
- { itemData: { item: i } },
- );
- returnData.push(...executionData);
- }
- }
- if (['file', 'folder'].includes(resource)) {
- if (operation === 'delete') {
- // ----------------------------------
- // delete
- // ----------------------------------
-
- const fileId = this.getNodeParameter('fileId', i, undefined, {
- extractValue: true,
- }) as string;
-
- await googleApiRequest.call(
- this,
- 'DELETE',
- `/drive/v3/files/${fileId}`,
- {},
- { supportsTeamDrives: true },
- );
-
- // If we are still here it did succeed
- const executionData = this.helpers.constructExecutionMetaData(
- this.helpers.returnJsonArray({
- fileId,
- success: true,
- }),
- { itemData: { item: i } },
- );
-
- returnData.push(...executionData);
- }
- if (operation === 'share') {
- const fileId = this.getNodeParameter('fileId', i, undefined, {
- extractValue: true,
- }) as string;
-
- const permissions = this.getNodeParameter('permissionsUi', i) as IDataObject;
-
- const shareOption = this.getNodeParameter('options', i);
-
- const body: IDataObject = {};
-
- const qs: IDataObject = {
- supportsTeamDrives: true,
- };
-
- if (permissions.permissionsValues) {
- Object.assign(body, permissions.permissionsValues);
- }
-
- Object.assign(qs, shareOption);
-
- const response = await googleApiRequest.call(
- this,
- 'POST',
- `/drive/v3/files/${fileId}/permissions`,
- body,
- qs,
- );
-
- const executionData = this.helpers.constructExecutionMetaData(
- this.helpers.returnJsonArray(response as IDataObject[]),
- { itemData: { item: i } },
- );
- returnData.push(...executionData);
- }
- }
- } catch (error) {
- if (this.continueOnFail()) {
- if (resource === 'file' && operation === 'download') {
- items[i].json = { error: error.message };
- } else {
- returnData.push({ json: { error: error.message } });
- }
- continue;
- }
- throw error;
- }
- }
- if (resource === 'file' && operation === 'download') {
- // For file downloads the files get attached to the existing items
- return this.prepareOutputData(items);
- } else {
- // For all other ones does the output items get replaced
- return this.prepareOutputData(returnData);
- }
+import type { INodeTypeBaseDescription, IVersionedNodeType } from 'n8n-workflow';
+import { VersionedNodeType } from 'n8n-workflow';
+
+import { GoogleDriveV1 } from './v1/GoogleDriveV1.node';
+import { GoogleDriveV2 } from './v2/GoogleDriveV2.node';
+
+export class GoogleDrive extends VersionedNodeType {
+ constructor() {
+ const baseDescription: INodeTypeBaseDescription = {
+ displayName: 'Google Drive',
+ name: 'googleDrive',
+ icon: 'file:googleDrive.svg',
+ group: ['input'],
+ subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
+ description: 'Access data on Google Drive',
+ defaultVersion: 3,
+ };
+
+ const nodeVersions: IVersionedNodeType['nodeVersions'] = {
+ 1: new GoogleDriveV1(baseDescription),
+ 2: new GoogleDriveV1(baseDescription),
+ 3: new GoogleDriveV2(baseDescription),
+ };
+
+ super(nodeVersions, baseDescription);
}
}
diff --git a/packages/nodes-base/nodes/Google/Drive/GoogleDriveTrigger.node.ts b/packages/nodes-base/nodes/Google/Drive/GoogleDriveTrigger.node.ts
index 08299f42a2..093b0a5ca9 100644
--- a/packages/nodes-base/nodes/Google/Drive/GoogleDriveTrigger.node.ts
+++ b/packages/nodes-base/nodes/Google/Drive/GoogleDriveTrigger.node.ts
@@ -9,10 +9,10 @@ import type {
} from 'n8n-workflow';
import { NodeApiError } from 'n8n-workflow';
-import { extractId, googleApiRequest, googleApiRequestAllItems } from './GenericFunctions';
+import { extractId, googleApiRequest, googleApiRequestAllItems } from './v1/GenericFunctions';
import moment from 'moment';
-import { fileSearch, folderSearch } from './SearchFunctions';
+import { fileSearch, folderSearch } from './v1/SearchFunctions';
export class GoogleDriveTrigger implements INodeType {
description: INodeTypeDescription = {
diff --git a/packages/nodes-base/nodes/Google/Drive/test/v2/node/drive/create.test.ts b/packages/nodes-base/nodes/Google/Drive/test/v2/node/drive/create.test.ts
new file mode 100644
index 0000000000..a86a7e8265
--- /dev/null
+++ b/packages/nodes-base/nodes/Google/Drive/test/v2/node/drive/create.test.ts
@@ -0,0 +1,78 @@
+import nock from 'nock';
+
+import * as create from '../../../../v2/actions/drive/create.operation';
+
+import * as transport from '../../../../v2/transport';
+
+import { createMockExecuteFunction, driveNode } from '../helpers';
+
+jest.mock('../../../../v2/transport', () => {
+ const originalModule = jest.requireActual('../../../../v2/transport');
+ return {
+ ...originalModule,
+ googleApiRequest: jest.fn(async function () {
+ return {};
+ }),
+ };
+});
+
+// eslint-disable-next-line @typescript-eslint/no-unused-vars, unused-imports/no-unused-imports
+import * as uuid from 'uuid';
+
+jest.mock('uuid', () => {
+ const originalModule = jest.requireActual('uuid');
+ return {
+ ...originalModule,
+ v4: jest.fn(function () {
+ return '430c0ca1-2498-472c-9d43-da0163839823';
+ }),
+ };
+});
+
+describe('test GoogleDriveV2: drive create', () => {
+ beforeAll(() => {
+ nock.disableNetConnect();
+ });
+
+ afterAll(() => {
+ nock.restore();
+ jest.unmock('../../../../v2/transport');
+ });
+
+ it('shuold be called with', async () => {
+ const nodeParameters = {
+ resource: 'drive',
+ name: 'newDrive',
+ options: {
+ capabilities: {
+ canComment: true,
+ canRename: true,
+ canTrashChildren: true,
+ },
+ colorRgb: '#451AD3',
+ hidden: false,
+ restrictions: {
+ driveMembersOnly: true,
+ },
+ },
+ };
+
+ const fakeExecuteFunction = createMockExecuteFunction(nodeParameters, driveNode);
+
+ await create.execute.call(fakeExecuteFunction, 0);
+
+ expect(transport.googleApiRequest).toBeCalledTimes(1);
+ expect(transport.googleApiRequest).toHaveBeenCalledWith(
+ 'POST',
+ '/drive/v3/drives',
+ {
+ capabilities: { canComment: true, canRename: true, canTrashChildren: true },
+ colorRgb: '#451AD3',
+ hidden: false,
+ name: 'newDrive',
+ restrictions: { driveMembersOnly: true },
+ },
+ { requestId: '430c0ca1-2498-472c-9d43-da0163839823' },
+ );
+ });
+});
diff --git a/packages/nodes-base/nodes/Google/Drive/test/v2/node/drive/deleteDrive.test.ts b/packages/nodes-base/nodes/Google/Drive/test/v2/node/drive/deleteDrive.test.ts
new file mode 100644
index 0000000000..e18249797e
--- /dev/null
+++ b/packages/nodes-base/nodes/Google/Drive/test/v2/node/drive/deleteDrive.test.ts
@@ -0,0 +1,50 @@
+import nock from 'nock';
+
+import * as deleteDrive from '../../../../v2/actions/drive/deleteDrive.operation';
+
+import * as transport from '../../../../v2/transport';
+
+import { createMockExecuteFunction, driveNode } from '../helpers';
+
+jest.mock('../../../../v2/transport', () => {
+ const originalModule = jest.requireActual('../../../../v2/transport');
+ return {
+ ...originalModule,
+ googleApiRequest: jest.fn(async function () {
+ return {};
+ }),
+ };
+});
+
+describe('test GoogleDriveV2: drive deleteDrive', () => {
+ beforeAll(() => {
+ nock.disableNetConnect();
+ });
+
+ afterAll(() => {
+ nock.restore();
+ jest.unmock('../../../../v2/transport');
+ });
+
+ it('shuold be called with', async () => {
+ const nodeParameters = {
+ resource: 'drive',
+ operation: 'deleteDrive',
+ driveId: {
+ __rl: true,
+ value: 'driveIDxxxxxx',
+ mode: 'id',
+ },
+ };
+
+ const fakeExecuteFunction = createMockExecuteFunction(nodeParameters, driveNode);
+
+ await deleteDrive.execute.call(fakeExecuteFunction, 0);
+
+ expect(transport.googleApiRequest).toBeCalledTimes(1);
+ expect(transport.googleApiRequest).toHaveBeenCalledWith(
+ 'DELETE',
+ '/drive/v3/drives/driveIDxxxxxx',
+ );
+ });
+});
diff --git a/packages/nodes-base/nodes/Google/Drive/test/v2/node/drive/get.test.ts b/packages/nodes-base/nodes/Google/Drive/test/v2/node/drive/get.test.ts
new file mode 100644
index 0000000000..d46cb35fb9
--- /dev/null
+++ b/packages/nodes-base/nodes/Google/Drive/test/v2/node/drive/get.test.ts
@@ -0,0 +1,55 @@
+import nock from 'nock';
+
+import * as get from '../../../../v2/actions/drive/get.operation';
+
+import * as transport from '../../../../v2/transport';
+
+import { createMockExecuteFunction, driveNode } from '../helpers';
+
+jest.mock('../../../../v2/transport', () => {
+ const originalModule = jest.requireActual('../../../../v2/transport');
+ return {
+ ...originalModule,
+ googleApiRequest: jest.fn(async function () {
+ return {};
+ }),
+ };
+});
+
+describe('test GoogleDriveV2: drive get', () => {
+ beforeAll(() => {
+ nock.disableNetConnect();
+ });
+
+ afterAll(() => {
+ nock.restore();
+ jest.unmock('../../../../v2/transport');
+ });
+
+ it('shuold be called with', async () => {
+ const nodeParameters = {
+ resource: 'drive',
+ operation: 'get',
+ driveId: {
+ __rl: true,
+ value: 'driveIDxxxxxx',
+ mode: 'id',
+ },
+ options: {
+ useDomainAdminAccess: true,
+ },
+ };
+
+ const fakeExecuteFunction = createMockExecuteFunction(nodeParameters, driveNode);
+
+ await get.execute.call(fakeExecuteFunction, 0);
+
+ expect(transport.googleApiRequest).toBeCalledTimes(1);
+ expect(transport.googleApiRequest).toHaveBeenCalledWith(
+ 'GET',
+ '/drive/v3/drives/driveIDxxxxxx',
+ {},
+ { useDomainAdminAccess: true },
+ );
+ });
+});
diff --git a/packages/nodes-base/nodes/Google/Drive/test/v2/node/drive/list.test.ts b/packages/nodes-base/nodes/Google/Drive/test/v2/node/drive/list.test.ts
new file mode 100644
index 0000000000..8693a00352
--- /dev/null
+++ b/packages/nodes-base/nodes/Google/Drive/test/v2/node/drive/list.test.ts
@@ -0,0 +1,78 @@
+import nock from 'nock';
+
+import * as list from '../../../../v2/actions/drive/list.operation';
+
+import * as transport from '../../../../v2/transport';
+
+import { createMockExecuteFunction, driveNode } from '../helpers';
+
+jest.mock('../../../../v2/transport', () => {
+ const originalModule = jest.requireActual('../../../../v2/transport');
+ return {
+ ...originalModule,
+ googleApiRequest: jest.fn(async function (method: string) {
+ if (method === 'GET') {
+ return {};
+ }
+ }),
+ googleApiRequestAllItems: jest.fn(async function (method: string) {
+ if (method === 'GET') {
+ return {};
+ }
+ }),
+ };
+});
+
+describe('test GoogleDriveV2: drive list', () => {
+ beforeAll(() => {
+ nock.disableNetConnect();
+ });
+
+ afterAll(() => {
+ nock.restore();
+ jest.unmock('../../../../v2/transport');
+ });
+
+ it('shuold be called with limit', async () => {
+ const nodeParameters = {
+ resource: 'drive',
+ operation: 'list',
+ limit: 20,
+ options: {},
+ };
+
+ const fakeExecuteFunction = createMockExecuteFunction(nodeParameters, driveNode);
+
+ await list.execute.call(fakeExecuteFunction, 0);
+
+ expect(transport.googleApiRequest).toBeCalledTimes(1);
+ expect(transport.googleApiRequest).toHaveBeenCalledWith(
+ 'GET',
+ '/drive/v3/drives',
+ {},
+ { pageSize: 20 },
+ );
+ });
+
+ it('shuold be called with returnAll true', async () => {
+ const nodeParameters = {
+ resource: 'drive',
+ operation: 'list',
+ returnAll: true,
+ options: {},
+ };
+
+ const fakeExecuteFunction = createMockExecuteFunction(nodeParameters, driveNode);
+
+ await list.execute.call(fakeExecuteFunction, 0);
+
+ expect(transport.googleApiRequestAllItems).toBeCalledTimes(1);
+ expect(transport.googleApiRequestAllItems).toHaveBeenCalledWith(
+ 'GET',
+ 'drives',
+ '/drive/v3/drives',
+ {},
+ {},
+ );
+ });
+});
diff --git a/packages/nodes-base/nodes/Google/Drive/test/v2/node/drive/update.test.ts b/packages/nodes-base/nodes/Google/Drive/test/v2/node/drive/update.test.ts
new file mode 100644
index 0000000000..534379fd54
--- /dev/null
+++ b/packages/nodes-base/nodes/Google/Drive/test/v2/node/drive/update.test.ts
@@ -0,0 +1,58 @@
+import nock from 'nock';
+
+import * as update from '../../../../v2/actions/drive/update.operation';
+
+import * as transport from '../../../../v2/transport';
+
+import { createMockExecuteFunction, driveNode } from '../helpers';
+
+jest.mock('../../../../v2/transport', () => {
+ const originalModule = jest.requireActual('../../../../v2/transport');
+ return {
+ ...originalModule,
+ googleApiRequest: jest.fn(async function () {
+ return {};
+ }),
+ };
+});
+
+describe('test GoogleDriveV2: drive update', () => {
+ beforeAll(() => {
+ nock.disableNetConnect();
+ });
+
+ afterAll(() => {
+ nock.restore();
+ jest.unmock('../../../../v2/transport');
+ });
+
+ it('shuold be called with', async () => {
+ const nodeParameters = {
+ resource: 'drive',
+ operation: 'update',
+ driveId: {
+ __rl: true,
+ value: 'sharedDriveIDxxxxx',
+ mode: 'id',
+ },
+ options: {
+ colorRgb: '#F4BEBE',
+ name: 'newName',
+ restrictions: {
+ driveMembersOnly: true,
+ },
+ },
+ };
+
+ const fakeExecuteFunction = createMockExecuteFunction(nodeParameters, driveNode);
+
+ await update.execute.call(fakeExecuteFunction, 0);
+
+ expect(transport.googleApiRequest).toBeCalledTimes(1);
+ expect(transport.googleApiRequest).toHaveBeenCalledWith(
+ 'PATCH',
+ '/drive/v3/drives/sharedDriveIDxxxxx',
+ { colorRgb: '#F4BEBE', name: 'newName', restrictions: { driveMembersOnly: true } },
+ );
+ });
+});
diff --git a/packages/nodes-base/nodes/Google/Drive/test/v2/node/file/copy.test.ts b/packages/nodes-base/nodes/Google/Drive/test/v2/node/file/copy.test.ts
new file mode 100644
index 0000000000..56e224b980
--- /dev/null
+++ b/packages/nodes-base/nodes/Google/Drive/test/v2/node/file/copy.test.ts
@@ -0,0 +1,76 @@
+import nock from 'nock';
+
+import * as copy from '../../../../v2/actions/file/copy.operation';
+
+import * as transport from '../../../../v2/transport';
+
+import { createMockExecuteFunction, driveNode } from '../helpers';
+
+jest.mock('../../../../v2/transport', () => {
+ const originalModule = jest.requireActual('../../../../v2/transport');
+ return {
+ ...originalModule,
+ googleApiRequest: jest.fn(async function () {
+ return {};
+ }),
+ };
+});
+
+describe('test GoogleDriveV2: file copy', () => {
+ beforeAll(() => {
+ nock.disableNetConnect();
+ });
+
+ afterAll(() => {
+ nock.restore();
+ jest.unmock('../../../../v2/transport');
+ });
+
+ it('shuold be called with', async () => {
+ const nodeParameters = {
+ operation: 'copy',
+ fileId: {
+ __rl: true,
+ value: 'fileIDxxxxxx',
+ mode: 'list',
+ cachedResultName: 'test01.png',
+ cachedResultUrl: 'https://drive.google.com/file/d/fileIDxxxxxx/view?usp=drivesdk',
+ },
+ name: 'copyImage.png',
+ sameFolder: false,
+ folderId: {
+ __rl: true,
+ value: 'folderIDxxxxxx',
+ mode: 'list',
+ cachedResultName: 'testFolder 3',
+ cachedResultUrl: 'https://drive.google.com/drive/folders/folderIDxxxxxx',
+ },
+ options: {
+ copyRequiresWriterPermission: true,
+ description: 'image copy',
+ },
+ };
+
+ const fakeExecuteFunction = createMockExecuteFunction(nodeParameters, driveNode);
+
+ await copy.execute.call(fakeExecuteFunction, 0);
+
+ expect(transport.googleApiRequest).toBeCalledTimes(1);
+ expect(transport.googleApiRequest).toBeCalledWith(
+ 'POST',
+ '/drive/v3/files/fileIDxxxxxx/copy',
+ {
+ copyRequiresWriterPermission: true,
+ description: 'image copy',
+ name: 'copyImage.png',
+ parents: ['folderIDxxxxxx'],
+ },
+ {
+ supportsAllDrives: true,
+ corpora: 'allDrives',
+ includeItemsFromAllDrives: true,
+ spaces: 'appDataFolder, drive',
+ },
+ );
+ });
+});
diff --git a/packages/nodes-base/nodes/Google/Drive/test/v2/node/file/createFromText.test.ts b/packages/nodes-base/nodes/Google/Drive/test/v2/node/file/createFromText.test.ts
new file mode 100644
index 0000000000..a9feae6642
--- /dev/null
+++ b/packages/nodes-base/nodes/Google/Drive/test/v2/node/file/createFromText.test.ts
@@ -0,0 +1,91 @@
+import nock from 'nock';
+
+import * as createFromText from '../../../../v2/actions/file/createFromText.operation';
+
+import * as transport from '../../../../v2/transport';
+
+import { createMockExecuteFunction, driveNode } from '../helpers';
+
+jest.mock('../../../../v2/transport', () => {
+ const originalModule = jest.requireActual('../../../../v2/transport');
+ return {
+ ...originalModule,
+ googleApiRequest: jest.fn(async function () {
+ return {};
+ }),
+ };
+});
+
+describe('test GoogleDriveV2: file createFromText', () => {
+ beforeAll(() => {
+ nock.disableNetConnect();
+ });
+
+ afterAll(() => {
+ nock.restore();
+ jest.unmock('../../../../v2/transport');
+ });
+
+ it('shuold be called with', async () => {
+ const nodeParameters = {
+ operation: 'createFromText',
+ content: 'hello drive!',
+ name: 'helloDrive.txt',
+ folderId: {
+ __rl: true,
+ value: 'folderIDxxxxxx',
+ mode: 'list',
+ cachedResultName: 'testFolder 3',
+ cachedResultUrl: 'https://drive.google.com/drive/folders/folderIDxxxxxx',
+ },
+ options: {
+ appPropertiesUi: {
+ appPropertyValues: [
+ {
+ key: 'appKey1',
+ value: 'appValue1',
+ },
+ ],
+ },
+ propertiesUi: {
+ propertyValues: [
+ {
+ key: 'prop1',
+ value: 'value1',
+ },
+ {
+ key: 'prop2',
+ value: 'value2',
+ },
+ ],
+ },
+ keepRevisionForever: true,
+ ocrLanguage: 'en',
+ useContentAsIndexableText: true,
+ },
+ };
+
+ const fakeExecuteFunction = createMockExecuteFunction(nodeParameters, driveNode);
+
+ await createFromText.execute.call(fakeExecuteFunction, 0);
+
+ expect(transport.googleApiRequest).toBeCalledTimes(1);
+ expect(transport.googleApiRequest).toHaveBeenCalledWith(
+ 'POST',
+ '/upload/drive/v3/files',
+ '\n\t\t\n--XXXXXX\t\t\nContent-Type: application/json; charset=UTF-8\t\t\n\n{"name":"helloDrive.txt","parents":["folderIDxxxxxx"],"mimeType":"text/plain","properties":{"prop1":"value1","prop2":"value2"},"appProperties":{"appKey1":"appValue1"}}\t\t\n--XXXXXX\t\t\nContent-Type: text/plain\t\t\nContent-Transfer-Encoding: base64\t\t\n\nhello drive!\t\t\n--XXXXXX--',
+ {
+ corpora: 'allDrives',
+ includeItemsFromAllDrives: true,
+ keepRevisionForever: true,
+ ocrLanguage: 'en',
+ spaces: 'appDataFolder, drive',
+ supportsAllDrives: true,
+ uploadType: 'multipart',
+ useContentAsIndexableText: true,
+ },
+ undefined,
+ { headers: { 'Content-Length': 12, 'Content-Type': 'multipart/related; boundary=XXXXXX' } },
+ );
+ });
+});
diff --git a/packages/nodes-base/nodes/Google/Drive/test/v2/node/file/deleteFile.test.ts b/packages/nodes-base/nodes/Google/Drive/test/v2/node/file/deleteFile.test.ts
new file mode 100644
index 0000000000..44f4d5e3ab
--- /dev/null
+++ b/packages/nodes-base/nodes/Google/Drive/test/v2/node/file/deleteFile.test.ts
@@ -0,0 +1,56 @@
+import nock from 'nock';
+
+import * as deleteFile from '../../../../v2/actions/file/deleteFile.operation';
+
+import * as transport from '../../../../v2/transport';
+
+import { createMockExecuteFunction, driveNode } from '../helpers';
+
+jest.mock('../../../../v2/transport', () => {
+ const originalModule = jest.requireActual('../../../../v2/transport');
+ return {
+ ...originalModule,
+ googleApiRequest: jest.fn(async function () {
+ return {};
+ }),
+ };
+});
+
+describe('test GoogleDriveV2: file deleteFile', () => {
+ beforeAll(() => {
+ nock.disableNetConnect();
+ });
+
+ afterAll(() => {
+ nock.restore();
+ jest.unmock('../../../../v2/transport');
+ });
+
+ it('shuold be called with', async () => {
+ const nodeParameters = {
+ operation: 'deleteFile',
+ fileId: {
+ __rl: true,
+ value: 'fileIDxxxxxx',
+ mode: 'list',
+ cachedResultName: 'test.txt',
+ cachedResultUrl: 'https://drive.google.com/file/d/fileIDxxxxxx/view?usp=drivesdk',
+ },
+ options: {
+ deletePermanently: true,
+ },
+ };
+
+ const fakeExecuteFunction = createMockExecuteFunction(nodeParameters, driveNode);
+
+ await deleteFile.execute.call(fakeExecuteFunction, 0);
+
+ expect(transport.googleApiRequest).toBeCalledTimes(1);
+ expect(transport.googleApiRequest).toHaveBeenCalledWith(
+ 'DELETE',
+ '/drive/v3/files/fileIDxxxxxx',
+ undefined,
+ { supportsAllDrives: true },
+ );
+ });
+});
diff --git a/packages/nodes-base/nodes/Google/Drive/test/v2/node/file/download.test.ts b/packages/nodes-base/nodes/Google/Drive/test/v2/node/file/download.test.ts
new file mode 100644
index 0000000000..33445637eb
--- /dev/null
+++ b/packages/nodes-base/nodes/Google/Drive/test/v2/node/file/download.test.ts
@@ -0,0 +1,64 @@
+import nock from 'nock';
+
+import * as download from '../../../../v2/actions/file/download.operation';
+
+import * as transport from '../../../../v2/transport';
+
+import { createMockExecuteFunction, driveNode } from '../helpers';
+
+jest.mock('../../../../v2/transport', () => {
+ const originalModule = jest.requireActual('../../../../v2/transport');
+ return {
+ ...originalModule,
+ googleApiRequest: jest.fn(async function () {
+ return {};
+ }),
+ };
+});
+
+describe('test GoogleDriveV2: file download', () => {
+ beforeAll(() => {
+ nock.disableNetConnect();
+ });
+
+ afterAll(() => {
+ nock.restore();
+ jest.unmock('../../../../v2/transport');
+ });
+
+ it('shuold be called with', async () => {
+ const nodeParameters = {
+ operation: 'deleteFile',
+ fileId: {
+ __rl: true,
+ value: 'fileIDxxxxxx',
+ mode: 'list',
+ cachedResultName: 'test.txt',
+ cachedResultUrl: 'https://drive.google.com/file/d/fileIDxxxxxx/view?usp=drivesdk',
+ },
+ options: {
+ deletePermanently: true,
+ },
+ };
+
+ const fakeExecuteFunction = createMockExecuteFunction(nodeParameters, driveNode);
+
+ await download.execute.call(fakeExecuteFunction, 0, { json: {} });
+
+ expect(transport.googleApiRequest).toBeCalledTimes(2);
+ expect(transport.googleApiRequest).toHaveBeenCalledWith(
+ 'GET',
+ '/drive/v3/files/fileIDxxxxxx',
+ {},
+ { fields: 'mimeType,name', supportsTeamDrives: true },
+ );
+ expect(transport.googleApiRequest).toHaveBeenCalledWith(
+ 'GET',
+ '/drive/v3/files/fileIDxxxxxx',
+ {},
+ { alt: 'media' },
+ undefined,
+ { encoding: null, json: false, resolveWithFullResponse: true, useStream: true },
+ );
+ });
+});
diff --git a/packages/nodes-base/nodes/Google/Drive/test/v2/node/file/move.test.ts b/packages/nodes-base/nodes/Google/Drive/test/v2/node/file/move.test.ts
new file mode 100644
index 0000000000..11aeb32adc
--- /dev/null
+++ b/packages/nodes-base/nodes/Google/Drive/test/v2/node/file/move.test.ts
@@ -0,0 +1,84 @@
+import nock from 'nock';
+
+import * as move from '../../../../v2/actions/file/move.operation';
+
+import * as transport from '../../../../v2/transport';
+
+import { createMockExecuteFunction, driveNode } from '../helpers';
+
+jest.mock('../../../../v2/transport', () => {
+ const originalModule = jest.requireActual('../../../../v2/transport');
+ return {
+ ...originalModule,
+ googleApiRequest: jest.fn(async function (method: string) {
+ if (method === 'GET') {
+ return {
+ parents: ['parentFolderIDxxxxxx'],
+ };
+ }
+ return {};
+ }),
+ };
+});
+
+describe('test GoogleDriveV2: file move', () => {
+ beforeAll(() => {
+ nock.disableNetConnect();
+ });
+
+ afterAll(() => {
+ nock.restore();
+ jest.unmock('../../../../v2/transport');
+ });
+
+ it('shuold be called with', async () => {
+ const nodeParameters = {
+ operation: 'move',
+ fileId: {
+ __rl: true,
+ value: 'fileIDxxxxxx',
+ mode: 'list',
+ cachedResultName: 'test.txt',
+ cachedResultUrl: 'https://drive.google.com/file/d/fileIDxxxxxx/view?usp=drivesdk',
+ },
+ folderId: {
+ __rl: true,
+ value: 'folderIDxxxxxx',
+ mode: 'list',
+ cachedResultName: 'testFolder1',
+ cachedResultUrl: 'https://drive.google.com/drive/folders/folderIDxxxxxx',
+ },
+ };
+
+ const fakeExecuteFunction = createMockExecuteFunction(nodeParameters, driveNode);
+
+ await move.execute.call(fakeExecuteFunction, 0);
+
+ expect(transport.googleApiRequest).toBeCalledTimes(2);
+ expect(transport.googleApiRequest).toHaveBeenCalledWith(
+ 'GET',
+ '/drive/v3/files/fileIDxxxxxx',
+ undefined,
+ {
+ corpora: 'allDrives',
+ fields: 'parents',
+ includeItemsFromAllDrives: true,
+ spaces: 'appDataFolder, drive',
+ supportsAllDrives: true,
+ },
+ );
+ expect(transport.googleApiRequest).toHaveBeenCalledWith(
+ 'PATCH',
+ '/drive/v3/files/fileIDxxxxxx',
+ undefined,
+ {
+ addParents: 'folderIDxxxxxx',
+ removeParents: 'parentFolderIDxxxxxx',
+ corpora: 'allDrives',
+ includeItemsFromAllDrives: true,
+ spaces: 'appDataFolder, drive',
+ supportsAllDrives: true,
+ },
+ );
+ });
+});
diff --git a/packages/nodes-base/nodes/Google/Drive/test/v2/node/file/share.test.ts b/packages/nodes-base/nodes/Google/Drive/test/v2/node/file/share.test.ts
new file mode 100644
index 0000000000..e69ab0adf0
--- /dev/null
+++ b/packages/nodes-base/nodes/Google/Drive/test/v2/node/file/share.test.ts
@@ -0,0 +1,74 @@
+import nock from 'nock';
+
+import * as share from '../../../../v2/actions/file/share.operation';
+
+import * as transport from '../../../../v2/transport';
+
+import { createMockExecuteFunction, driveNode } from '../helpers';
+
+jest.mock('../../../../v2/transport', () => {
+ const originalModule = jest.requireActual('../../../../v2/transport');
+ return {
+ ...originalModule,
+ googleApiRequest: jest.fn(async function () {
+ return {};
+ }),
+ };
+});
+
+describe('test GoogleDriveV2: file share', () => {
+ beforeAll(() => {
+ nock.disableNetConnect();
+ });
+
+ afterAll(() => {
+ nock.restore();
+ jest.unmock('../../../../v2/transport');
+ });
+
+ it('shuold be called with', async () => {
+ const nodeParameters = {
+ operation: 'share',
+ fileId: {
+ __rl: true,
+ value: 'fileIDxxxxxx',
+ mode: 'list',
+ cachedResultName: 'test.txt',
+ cachedResultUrl: 'https://drive.google.com/file/d/fileIDxxxxxx/view?usp=drivesdk',
+ },
+ permissionsUi: {
+ permissionsValues: {
+ role: 'owner',
+ type: 'user',
+ emailAddress: 'user@gmail.com',
+ },
+ },
+ options: {
+ emailMessage: 'some message',
+ moveToNewOwnersRoot: true,
+ sendNotificationEmail: true,
+ transferOwnership: true,
+ useDomainAdminAccess: true,
+ },
+ };
+
+ const fakeExecuteFunction = createMockExecuteFunction(nodeParameters, driveNode);
+
+ await share.execute.call(fakeExecuteFunction, 0);
+
+ expect(transport.googleApiRequest).toBeCalledTimes(1);
+ expect(transport.googleApiRequest).toHaveBeenCalledWith(
+ 'POST',
+ '/drive/v3/files/fileIDxxxxxx/permissions',
+ { emailAddress: 'user@gmail.com', role: 'owner', type: 'user' },
+ {
+ emailMessage: 'some message',
+ moveToNewOwnersRoot: true,
+ sendNotificationEmail: true,
+ supportsAllDrives: true,
+ transferOwnership: true,
+ useDomainAdminAccess: true,
+ },
+ );
+ });
+});
diff --git a/packages/nodes-base/nodes/Google/Drive/test/v2/node/file/update.test.ts b/packages/nodes-base/nodes/Google/Drive/test/v2/node/file/update.test.ts
new file mode 100644
index 0000000000..f21a02f02e
--- /dev/null
+++ b/packages/nodes-base/nodes/Google/Drive/test/v2/node/file/update.test.ts
@@ -0,0 +1,66 @@
+import nock from 'nock';
+
+import * as update from '../../../../v2/actions/file/update.operation';
+
+import * as transport from '../../../../v2/transport';
+
+import { createMockExecuteFunction, driveNode } from '../helpers';
+
+jest.mock('../../../../v2/transport', () => {
+ const originalModule = jest.requireActual('../../../../v2/transport');
+ return {
+ ...originalModule,
+ googleApiRequest: jest.fn(async function () {
+ return {};
+ }),
+ };
+});
+
+describe('test GoogleDriveV2: file update', () => {
+ beforeAll(() => {
+ nock.disableNetConnect();
+ });
+
+ afterAll(() => {
+ nock.restore();
+ jest.unmock('../../../../v2/transport');
+ });
+
+ it('shuold be called with', async () => {
+ const nodeParameters = {
+ operation: 'update',
+ fileId: {
+ __rl: true,
+ value: 'fileIDxxxxxx',
+ mode: 'list',
+ cachedResultName: 'test.txt',
+ cachedResultUrl: 'https://drive.google.com/file/d/fileIDxxxxxx/view?usp=drivesdk',
+ },
+ newUpdatedFileName: 'test2.txt',
+ options: {
+ keepRevisionForever: true,
+ ocrLanguage: 'en',
+ useContentAsIndexableText: true,
+ fields: ['hasThumbnail', 'starred'],
+ },
+ };
+
+ const fakeExecuteFunction = createMockExecuteFunction(nodeParameters, driveNode);
+
+ await update.execute.call(fakeExecuteFunction, 0);
+
+ expect(transport.googleApiRequest).toBeCalledTimes(1);
+ expect(transport.googleApiRequest).toHaveBeenCalledWith(
+ 'PATCH',
+ '/drive/v3/files/fileIDxxxxxx',
+ { name: 'test2.txt' },
+ {
+ fields: 'hasThumbnail, starred',
+ keepRevisionForever: true,
+ ocrLanguage: 'en',
+ supportsAllDrives: true,
+ useContentAsIndexableText: true,
+ },
+ );
+ });
+});
diff --git a/packages/nodes-base/nodes/Google/Drive/test/v2/node/file/upload.test.ts b/packages/nodes-base/nodes/Google/Drive/test/v2/node/file/upload.test.ts
new file mode 100644
index 0000000000..fdd6e30f68
--- /dev/null
+++ b/packages/nodes-base/nodes/Google/Drive/test/v2/node/file/upload.test.ts
@@ -0,0 +1,96 @@
+import nock from 'nock';
+
+import * as upload from '../../../../v2/actions/file/upload.operation';
+
+import * as transport from '../../../../v2/transport';
+import * as utils from '../../../../v2/helpers/utils';
+
+import { createMockExecuteFunction, driveNode } from '../helpers';
+
+jest.mock('../../../../v2/transport', () => {
+ const originalModule = jest.requireActual('../../../../v2/transport');
+ return {
+ ...originalModule,
+ googleApiRequest: jest.fn(async function (method: string) {
+ if (method === 'POST') {
+ return {
+ headers: { location: 'someLocation' },
+ };
+ }
+ return {};
+ }),
+ };
+});
+
+jest.mock('../../../../v2/helpers/utils', () => {
+ const originalModule = jest.requireActual('../../../../v2/helpers/utils');
+ return {
+ ...originalModule,
+ getItemBinaryData: jest.fn(async function () {
+ return {
+ contentLength: '123',
+ fileContent: 'Hello Drive!',
+ originalFilename: 'original.txt',
+ mimeType: 'text/plain',
+ };
+ }),
+ };
+});
+
+describe('test GoogleDriveV2: file upload', () => {
+ beforeAll(() => {
+ nock.disableNetConnect();
+ });
+
+ afterAll(() => {
+ nock.restore();
+ jest.unmock('../../../../v2/transport');
+ jest.unmock('../../../../v2/helpers/utils');
+ });
+
+ it('shuold be called with', async () => {
+ const nodeParameters = {
+ name: 'newFile.txt',
+ folderId: {
+ __rl: true,
+ value: 'folderIDxxxxxx',
+ mode: 'list',
+ cachedResultName: 'testFolder 3',
+ cachedResultUrl: 'https://drive.google.com/drive/folders/folderIDxxxxxx',
+ },
+ options: {
+ simplifyOutput: true,
+ },
+ };
+
+ const fakeExecuteFunction = createMockExecuteFunction(nodeParameters, driveNode);
+
+ await upload.execute.call(fakeExecuteFunction, 0);
+
+ expect(transport.googleApiRequest).toBeCalledTimes(2);
+
+ expect(transport.googleApiRequest).toHaveBeenCalledWith(
+ 'POST',
+ '/upload/drive/v3/files',
+ undefined,
+ { uploadType: 'resumable' },
+ undefined,
+ { resolveWithFullResponse: true },
+ );
+ expect(transport.googleApiRequest).toHaveBeenCalledWith(
+ 'PATCH',
+ '/drive/v3/files/undefined',
+ { mimeType: 'text/plain', name: 'newFile.txt', originalFilename: 'original.txt' },
+ {
+ addParents: 'folderIDxxxxxx',
+ supportsAllDrives: true,
+ corpora: 'allDrives',
+ includeItemsFromAllDrives: true,
+ spaces: 'appDataFolder, drive',
+ },
+ );
+
+ expect(utils.getItemBinaryData).toBeCalledTimes(1);
+ expect(utils.getItemBinaryData).toHaveBeenCalled();
+ });
+});
diff --git a/packages/nodes-base/nodes/Google/Drive/test/v2/node/fileFolder/search.test.ts b/packages/nodes-base/nodes/Google/Drive/test/v2/node/fileFolder/search.test.ts
new file mode 100644
index 0000000000..3f10deeac3
--- /dev/null
+++ b/packages/nodes-base/nodes/Google/Drive/test/v2/node/fileFolder/search.test.ts
@@ -0,0 +1,119 @@
+import nock from 'nock';
+
+import * as search from '../../../../v2/actions/fileFolder/search.operation';
+
+import * as transport from '../../../../v2/transport';
+
+import { createMockExecuteFunction, driveNode } from '../helpers';
+
+jest.mock('../../../../v2/transport', () => {
+ const originalModule = jest.requireActual('../../../../v2/transport');
+ return {
+ ...originalModule,
+ googleApiRequest: jest.fn(async function (method: string) {
+ if (method === 'GET') {
+ return {};
+ }
+ }),
+ googleApiRequestAllItems: jest.fn(async function (method: string) {
+ if (method === 'GET') {
+ return {};
+ }
+ }),
+ };
+});
+
+describe('test GoogleDriveV2: fileFolder search', () => {
+ beforeAll(() => {
+ nock.disableNetConnect();
+ });
+
+ afterAll(() => {
+ nock.restore();
+ jest.unmock('../../../../v2/transport');
+ });
+
+ it('returnAll = false', async () => {
+ const nodeParameters = {
+ searchMethod: 'name',
+ resource: 'fileFolder',
+ queryString: 'test',
+ returnAll: false,
+ limit: 2,
+ filter: {
+ whatToSearch: 'files',
+ fileTypes: ['application/vnd.google-apps.document'],
+ },
+ options: {
+ fields: ['id', 'name', 'starred', 'version'],
+ },
+ };
+
+ const fakeExecuteFunction = createMockExecuteFunction(nodeParameters, driveNode);
+
+ await search.execute.call(fakeExecuteFunction, 0);
+
+ expect(transport.googleApiRequest).toBeCalledTimes(1);
+ expect(transport.googleApiRequest).toBeCalledWith('GET', '/drive/v3/files', undefined, {
+ corpora: 'allDrives',
+ fields: 'nextPageToken, files(id, name, starred, version)',
+ includeItemsFromAllDrives: true,
+ pageSize: 2,
+ q: "name contains 'test' and mimeType != 'application/vnd.google-apps.folder' and trashed = false and (mimeType = 'application/vnd.google-apps.document')",
+ spaces: 'appDataFolder, drive',
+ supportsAllDrives: true,
+ });
+ });
+
+ it('returnAll = true', async () => {
+ const nodeParameters = {
+ resource: 'fileFolder',
+ searchMethod: 'query',
+ queryString: 'test',
+ returnAll: true,
+ filter: {
+ driveId: {
+ __rl: true,
+ value: 'driveID000000123',
+ mode: 'list',
+ cachedResultName: 'sharedDrive',
+ cachedResultUrl: 'https://drive.google.com/drive/folders/driveID000000123',
+ },
+ folderId: {
+ __rl: true,
+ value: 'folderID000000123',
+ mode: 'list',
+ cachedResultName: 'testFolder 3',
+ cachedResultUrl: 'https://drive.google.com/drive/folders/folderID000000123',
+ },
+ whatToSearch: 'all',
+ fileTypes: ['*'],
+ includeTrashed: true,
+ },
+ options: {
+ fields: ['permissions', 'mimeType'],
+ },
+ };
+
+ const fakeExecuteFunction = createMockExecuteFunction(nodeParameters, driveNode);
+
+ await search.execute.call(fakeExecuteFunction, 0);
+
+ expect(transport.googleApiRequestAllItems).toBeCalledTimes(1);
+ expect(transport.googleApiRequestAllItems).toBeCalledWith(
+ 'GET',
+ 'files',
+ '/drive/v3/files',
+ {},
+ {
+ corpora: 'drive',
+ driveId: 'driveID000000123',
+ fields: 'nextPageToken, files(permissions, mimeType)',
+ includeItemsFromAllDrives: true,
+ q: "test and 'folderID000000123' in parents",
+ spaces: 'appDataFolder, drive',
+ supportsAllDrives: true,
+ },
+ );
+ });
+});
diff --git a/packages/nodes-base/nodes/Google/Drive/test/v2/node/folder/create.test.ts b/packages/nodes-base/nodes/Google/Drive/test/v2/node/folder/create.test.ts
new file mode 100644
index 0000000000..37858c78dc
--- /dev/null
+++ b/packages/nodes-base/nodes/Google/Drive/test/v2/node/folder/create.test.ts
@@ -0,0 +1,68 @@
+import nock from 'nock';
+
+import * as create from '../../../../v2/actions/folder/create.operation';
+
+import * as transport from '../../../../v2/transport';
+
+import { createMockExecuteFunction, driveNode } from '../helpers';
+
+jest.mock('../../../../v2/transport', () => {
+ const originalModule = jest.requireActual('../../../../v2/transport');
+ return {
+ ...originalModule,
+ googleApiRequest: jest.fn(async function () {
+ return {};
+ }),
+ };
+});
+
+describe('test GoogleDriveV2: folder create', () => {
+ beforeAll(() => {
+ nock.disableNetConnect();
+ });
+
+ afterAll(() => {
+ nock.restore();
+ jest.unmock('../../../../v2/transport');
+ });
+
+ it('shuold be called with', async () => {
+ const nodeParameters = {
+ resource: 'folder',
+ name: 'testFolder 2',
+ folderId: {
+ __rl: true,
+ value: 'root',
+ mode: 'list',
+ cachedResultName: 'root',
+ cachedResultUrl: 'https://drive.google.com/drive',
+ },
+ options: {
+ folderColorRgb: '#167D08',
+ },
+ };
+
+ const fakeExecuteFunction = createMockExecuteFunction(nodeParameters, driveNode);
+
+ await create.execute.call(fakeExecuteFunction, 0);
+
+ expect(transport.googleApiRequest).toBeCalledTimes(1);
+ expect(transport.googleApiRequest).toHaveBeenCalledWith(
+ 'POST',
+ '/drive/v3/files',
+ {
+ folderColorRgb: '#167D08',
+ mimeType: 'application/vnd.google-apps.folder',
+ name: 'testFolder 2',
+ parents: ['root'],
+ },
+ {
+ fields: undefined,
+ includeItemsFromAllDrives: true,
+ supportsAllDrives: true,
+ spaces: 'appDataFolder, drive',
+ corpora: 'allDrives',
+ },
+ );
+ });
+});
diff --git a/packages/nodes-base/nodes/Google/Drive/test/v2/node/folder/deleteFolder.test.ts b/packages/nodes-base/nodes/Google/Drive/test/v2/node/folder/deleteFolder.test.ts
new file mode 100644
index 0000000000..b141d206a1
--- /dev/null
+++ b/packages/nodes-base/nodes/Google/Drive/test/v2/node/folder/deleteFolder.test.ts
@@ -0,0 +1,80 @@
+import nock from 'nock';
+
+import * as deleteFolder from '../../../../v2/actions/folder/deleteFolder.operation';
+
+import * as transport from '../../../../v2/transport';
+
+import { createMockExecuteFunction, driveNode } from '../helpers';
+
+jest.mock('../../../../v2/transport', () => {
+ const originalModule = jest.requireActual('../../../../v2/transport');
+ return {
+ ...originalModule,
+ googleApiRequest: jest.fn(async function () {
+ return {};
+ }),
+ };
+});
+
+describe('test GoogleDriveV2: folder deleteFolder', () => {
+ beforeAll(() => {
+ nock.disableNetConnect();
+ });
+
+ afterAll(() => {
+ nock.restore();
+ jest.unmock('../../../../v2/transport');
+ });
+
+ it('shuold be called with PATCH', async () => {
+ const nodeParameters = {
+ resource: 'folder',
+ operation: 'deleteFolder',
+ folderNoRootId: {
+ __rl: true,
+ value: 'folderIDxxxxxx',
+ mode: 'list',
+ cachedResultName: 'testFolder 2',
+ cachedResultUrl: 'https://drive.google.com/drive/folders/folderIDxxxxxx',
+ },
+ options: {},
+ };
+
+ const fakeExecuteFunction = createMockExecuteFunction(nodeParameters, driveNode);
+
+ await deleteFolder.execute.call(fakeExecuteFunction, 0);
+
+ expect(transport.googleApiRequest).toHaveBeenCalledWith(
+ 'PATCH',
+ '/drive/v3/files/folderIDxxxxxx',
+ { trashed: true },
+ { supportsAllDrives: true },
+ );
+ });
+
+ it('shuold be called with DELETE', async () => {
+ const nodeParameters = {
+ resource: 'folder',
+ operation: 'deleteFolder',
+ folderNoRootId: {
+ __rl: true,
+ value: 'folderIDxxxxxx',
+ mode: 'list',
+ cachedResultName: 'testFolder 2',
+ cachedResultUrl: 'https://drive.google.com/drive/folders/folderIDxxxxxx',
+ },
+ options: { deletePermanently: true },
+ };
+
+ const fakeExecuteFunction = createMockExecuteFunction(nodeParameters, driveNode);
+
+ await deleteFolder.execute.call(fakeExecuteFunction, 0);
+
+ expect(transport.googleApiRequest).toHaveBeenCalledWith(
+ 'DELETE',
+ '/drive/v3/files/folderIDxxxxxx',
+ undefined,
+ { supportsAllDrives: true },
+ );
+ });
+});
diff --git a/packages/nodes-base/nodes/Google/Drive/test/v2/node/folder/share.test.ts b/packages/nodes-base/nodes/Google/Drive/test/v2/node/folder/share.test.ts
new file mode 100644
index 0000000000..d5854e38f5
--- /dev/null
+++ b/packages/nodes-base/nodes/Google/Drive/test/v2/node/folder/share.test.ts
@@ -0,0 +1,64 @@
+import nock from 'nock';
+
+import * as share from '../../../../v2/actions/folder/share.operation';
+
+import * as transport from '../../../../v2/transport';
+
+import { createMockExecuteFunction, driveNode } from '../helpers';
+
+jest.mock('../../../../v2/transport', () => {
+ const originalModule = jest.requireActual('../../../../v2/transport');
+ return {
+ ...originalModule,
+ googleApiRequest: jest.fn(async function () {
+ return {};
+ }),
+ };
+});
+
+describe('test GoogleDriveV2: folder share', () => {
+ beforeAll(() => {
+ nock.disableNetConnect();
+ });
+
+ afterAll(() => {
+ nock.restore();
+ jest.unmock('../../../../v2/transport');
+ });
+
+ it('shuold be called with', async () => {
+ const nodeParameters = {
+ resource: 'folder',
+ operation: 'share',
+ folderNoRootId: {
+ __rl: true,
+ value: 'folderIDxxxxxx',
+ mode: 'list',
+ cachedResultName: 'testFolder 2',
+ cachedResultUrl: 'https://drive.google.com/drive/folders/folderIDxxxxxx',
+ },
+ permissionsUi: {
+ permissionsValues: {
+ role: 'reader',
+ type: 'anyone',
+ allowFileDiscovery: true,
+ },
+ },
+ options: {
+ moveToNewOwnersRoot: true,
+ },
+ };
+
+ const fakeExecuteFunction = createMockExecuteFunction(nodeParameters, driveNode);
+
+ await share.execute.call(fakeExecuteFunction, 0);
+
+ expect(transport.googleApiRequest).toBeCalledTimes(1);
+ expect(transport.googleApiRequest).toHaveBeenCalledWith(
+ 'POST',
+ '/drive/v3/files/folderIDxxxxxx/permissions',
+ { allowFileDiscovery: true, role: 'reader', type: 'anyone' },
+ { moveToNewOwnersRoot: true, supportsAllDrives: true },
+ );
+ });
+});
diff --git a/packages/nodes-base/nodes/Google/Drive/test/v2/node/helpers.ts b/packages/nodes-base/nodes/Google/Drive/test/v2/node/helpers.ts
new file mode 100644
index 0000000000..b04a33e2e2
--- /dev/null
+++ b/packages/nodes-base/nodes/Google/Drive/test/v2/node/helpers.ts
@@ -0,0 +1,42 @@
+import type { IDataObject, IExecuteFunctions, IGetNodeParameterOptions, INode } from 'n8n-workflow';
+
+import { get } from 'lodash';
+import { constructExecutionMetaData, returnJsonArray } from 'n8n-core';
+
+export const driveNode: INode = {
+ id: '11',
+ name: 'Google Drive node',
+ typeVersion: 3,
+ type: 'n8n-nodes-base.googleDrive',
+ position: [42, 42],
+ parameters: {},
+};
+
+export const createMockExecuteFunction = (
+ nodeParameters: IDataObject,
+ node: INode,
+ continueOnFail = false,
+) => {
+ const fakeExecuteFunction = {
+ getNodeParameter(
+ parameterName: string,
+ _itemIndex: number,
+ fallbackValue?: IDataObject | undefined,
+ options?: IGetNodeParameterOptions | undefined,
+ ) {
+ const parameter = options?.extractValue ? `${parameterName}.value` : parameterName;
+ return get(nodeParameters, parameter, fallbackValue);
+ },
+ getNode() {
+ return node;
+ },
+ helpers: {
+ constructExecutionMetaData,
+ returnJsonArray,
+ prepareBinaryData: () => {},
+ httpRequest: () => {},
+ },
+ continueOnFail: () => continueOnFail,
+ } as unknown as IExecuteFunctions;
+ return fakeExecuteFunction;
+};
diff --git a/packages/nodes-base/nodes/Google/Drive/test/v2/utils.test.ts b/packages/nodes-base/nodes/Google/Drive/test/v2/utils.test.ts
new file mode 100644
index 0000000000..7af5aa9abc
--- /dev/null
+++ b/packages/nodes-base/nodes/Google/Drive/test/v2/utils.test.ts
@@ -0,0 +1,125 @@
+import {
+ prepareQueryString,
+ setFileProperties,
+ setUpdateCommonParams,
+} from '../../v2/helpers/utils';
+
+describe('test GoogleDriveV2, prepareQueryString', () => {
+ it('should return id, name', () => {
+ const fields = undefined;
+
+ const result = prepareQueryString(fields);
+
+ expect(result).toEqual('id, name');
+ });
+
+ it('should return *', () => {
+ const fields = ['*'];
+
+ const result = prepareQueryString(fields);
+
+ expect(result).toEqual('*');
+ });
+
+ it('should return string joined by ,', () => {
+ const fields = ['id', 'name', 'mimeType'];
+
+ const result = prepareQueryString(fields);
+
+ expect(result).toEqual('id, name, mimeType');
+ });
+});
+
+describe('test GoogleDriveV2, setFileProperties', () => {
+ it('should return empty object', () => {
+ const body = {};
+ const options = {};
+
+ const result = setFileProperties(body, options);
+
+ expect(result).toEqual({});
+ });
+
+ it('should return object with properties', () => {
+ const body = {};
+ const options = {
+ propertiesUi: {
+ propertyValues: [
+ {
+ key: 'propertyKey1',
+ value: 'propertyValue1',
+ },
+ {
+ key: 'propertyKey2',
+ value: 'propertyValue2',
+ },
+ ],
+ },
+ };
+
+ const result = setFileProperties(body, options);
+
+ expect(result).toEqual({
+ properties: {
+ propertyKey1: 'propertyValue1',
+ propertyKey2: 'propertyValue2',
+ },
+ });
+ });
+
+ it('should return object with appProperties', () => {
+ const body = {};
+ const options = {
+ appPropertiesUi: {
+ appPropertyValues: [
+ {
+ key: 'appPropertyKey1',
+ value: 'appPropertyValue1',
+ },
+ {
+ key: 'appPropertyKey2',
+ value: 'appPropertyValue2',
+ },
+ ],
+ },
+ };
+
+ const result = setFileProperties(body, options);
+
+ expect(result).toEqual({
+ appProperties: {
+ appPropertyKey1: 'appPropertyValue1',
+ appPropertyKey2: 'appPropertyValue2',
+ },
+ });
+ });
+});
+
+describe('test GoogleDriveV2, setUpdateCommonParams', () => {
+ it('should return empty object', () => {
+ const qs = {};
+ const options = {};
+
+ const result = setUpdateCommonParams(qs, options);
+
+ expect(result).toEqual({});
+ });
+
+ it('should return qs with params', () => {
+ const options = {
+ useContentAsIndexableText: true,
+ keepRevisionForever: true,
+ ocrLanguage: 'en',
+ trashed: true,
+ includePermissionsForView: 'published',
+ };
+
+ const qs = setUpdateCommonParams({}, options);
+
+ expect(qs).toEqual({
+ useContentAsIndexableText: true,
+ keepRevisionForever: true,
+ ocrLanguage: 'en',
+ });
+ });
+});
diff --git a/packages/nodes-base/nodes/Google/Drive/GenericFunctions.ts b/packages/nodes-base/nodes/Google/Drive/v1/GenericFunctions.ts
similarity index 97%
rename from packages/nodes-base/nodes/Google/Drive/GenericFunctions.ts
rename to packages/nodes-base/nodes/Google/Drive/v1/GenericFunctions.ts
index e9c213f486..b3600fca2f 100644
--- a/packages/nodes-base/nodes/Google/Drive/GenericFunctions.ts
+++ b/packages/nodes-base/nodes/Google/Drive/v1/GenericFunctions.ts
@@ -10,7 +10,7 @@ import type {
} from 'n8n-workflow';
import { NodeApiError } from 'n8n-workflow';
-import { getGoogleAccessToken } from '../GenericFunctions';
+import { getGoogleAccessToken } from '../../GenericFunctions';
export async function googleApiRequest(
this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions | IPollFunctions,
diff --git a/packages/nodes-base/nodes/Google/Drive/v1/GoogleDriveV1.node.ts b/packages/nodes-base/nodes/Google/Drive/v1/GoogleDriveV1.node.ts
new file mode 100644
index 0000000000..08c254daa2
--- /dev/null
+++ b/packages/nodes-base/nodes/Google/Drive/v1/GoogleDriveV1.node.ts
@@ -0,0 +1,2750 @@
+/* eslint-disable n8n-nodes-base/node-filename-against-convention */
+import type {
+ IBinaryKeyData,
+ IDataObject,
+ IExecuteFunctions,
+ INodeExecutionData,
+ INodeType,
+ INodeTypeBaseDescription,
+ INodeTypeDescription,
+} from 'n8n-workflow';
+import { BINARY_ENCODING } from 'n8n-workflow';
+
+import { googleApiRequest, googleApiRequestAllItems } from './GenericFunctions';
+
+import { v4 as uuid } from 'uuid';
+import type { Readable } from 'stream';
+import { driveSearch, fileSearch, folderSearch } from './SearchFunctions';
+
+import { oldVersionNotice } from '@utils/descriptions';
+
+const UPLOAD_CHUNK_SIZE = 256 * 1024;
+
+const versionDescription: INodeTypeDescription = {
+ displayName: 'Google Drive',
+ name: 'googleDrive',
+ icon: 'file:googleDrive.svg',
+ group: ['input'],
+ version: [1, 2],
+ subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
+ description: 'Access data on Google Drive',
+ defaults: {
+ name: 'Google Drive',
+ },
+ inputs: ['main'],
+ outputs: ['main'],
+ credentials: [
+ {
+ name: 'googleApi',
+ required: true,
+ displayOptions: {
+ show: {
+ authentication: ['serviceAccount'],
+ },
+ },
+ },
+ {
+ name: 'googleDriveOAuth2Api',
+ required: true,
+ displayOptions: {
+ show: {
+ authentication: ['oAuth2'],
+ },
+ },
+ },
+ ],
+ properties: [
+ oldVersionNotice,
+ {
+ displayName: 'Authentication',
+ name: 'authentication',
+ type: 'options',
+ options: [
+ {
+ name: 'Service Account',
+ value: 'serviceAccount',
+ },
+ {
+ name: 'OAuth2',
+ value: 'oAuth2',
+ },
+ ],
+ default: 'serviceAccount',
+ displayOptions: {
+ show: {
+ '@version': [1],
+ },
+ },
+ },
+ {
+ displayName: 'Authentication',
+ name: 'authentication',
+ type: 'options',
+ options: [
+ {
+ // eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
+ name: 'OAuth2 (recommended)',
+ value: 'oAuth2',
+ },
+ {
+ name: 'Service Account',
+ value: 'serviceAccount',
+ },
+ ],
+ default: 'oAuth2',
+ displayOptions: {
+ show: {
+ '@version': [2],
+ },
+ },
+ },
+ {
+ displayName: 'Resource',
+ name: 'resource',
+ type: 'options',
+ noDataExpression: true,
+ options: [
+ {
+ name: 'Drive',
+ value: 'drive',
+ },
+ {
+ name: 'File',
+ value: 'file',
+ },
+ {
+ name: 'Folder',
+ value: 'folder',
+ },
+ ],
+ default: 'file',
+ },
+
+ // ----------------------------------
+ // operations
+ // ----------------------------------
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ noDataExpression: true,
+ displayOptions: {
+ show: {
+ resource: ['file'],
+ },
+ },
+ options: [
+ {
+ name: 'Copy',
+ value: 'copy',
+ description: 'Copy a file',
+ action: 'Copy a file',
+ },
+ {
+ name: 'Delete',
+ value: 'delete',
+ description: 'Delete a file',
+ action: 'Delete a file',
+ },
+ {
+ name: 'Download',
+ value: 'download',
+ description: 'Download a file',
+ action: 'Download a file',
+ },
+ {
+ name: 'List',
+ value: 'list',
+ description: 'List files and folders',
+ action: 'List a file',
+ },
+ {
+ name: 'Share',
+ value: 'share',
+ description: 'Share a file',
+ action: 'Share a file',
+ },
+ {
+ name: 'Update',
+ value: 'update',
+ description: 'Update a file',
+ action: 'Update a file',
+ },
+ {
+ name: 'Upload',
+ value: 'upload',
+ description: 'Upload a file',
+ action: 'Upload a file',
+ },
+ ],
+ default: 'upload',
+ },
+
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ noDataExpression: true,
+ displayOptions: {
+ show: {
+ resource: ['folder'],
+ },
+ },
+ options: [
+ {
+ name: 'Create',
+ value: 'create',
+ description: 'Create a folder',
+ action: 'Create a folder',
+ },
+ {
+ name: 'Delete',
+ value: 'delete',
+ description: 'Delete a folder',
+ action: 'Delete a folder',
+ },
+ {
+ name: 'Share',
+ value: 'share',
+ description: 'Share a folder',
+ action: 'Share a folder',
+ },
+ ],
+ default: 'create',
+ },
+
+ // ----------------------------------
+ // file
+ // ----------------------------------
+
+ {
+ displayName: 'File',
+ name: 'fileId',
+ type: 'resourceLocator',
+ default: { mode: 'list', value: '' },
+ required: true,
+ modes: [
+ {
+ displayName: 'File',
+ name: 'list',
+ type: 'list',
+ placeholder: 'Select a file...',
+ typeOptions: {
+ searchListMethod: 'fileSearch',
+ searchable: true,
+ },
+ },
+ {
+ displayName: 'Link',
+ name: 'url',
+ type: 'string',
+ placeholder:
+ 'https://drive.google.com/file/d/1anGBg0b5re2VtF2bKu201_a-Vnz5BHq9Y4r-yBDAj5A/edit',
+ extractValue: {
+ type: 'regex',
+ regex:
+ 'https:\\/\\/(?:drive|docs)\\.google\\.com\\/\\w+\\/d\\/([0-9a-zA-Z\\-_]+)(?:\\/.*|)',
+ },
+ validation: [
+ {
+ type: 'regex',
+ properties: {
+ regex:
+ 'https:\\/\\/(?:drive|docs)\\.google.com\\/\\w+\\/d\\/([0-9a-zA-Z\\-_]+)(?:\\/.*|)',
+ errorMessage: 'Not a valid Google Drive File URL',
+ },
+ },
+ ],
+ },
+ {
+ displayName: 'ID',
+ name: 'id',
+ type: 'string',
+ placeholder: '1anGBg0b5re2VtF2bKu201_a-Vnz5BHq9Y4r-yBDAj5A',
+ validation: [
+ {
+ type: 'regex',
+ properties: {
+ regex: '[a-zA-Z0-9\\-_]{2,}',
+ errorMessage: 'Not a valid Google Drive File ID',
+ },
+ },
+ ],
+ url: '=https://drive.google.com/file/d/{{$value}}/view',
+ },
+ ],
+ displayOptions: {
+ show: {
+ operation: ['download', 'copy', 'update', 'delete', 'share'],
+ resource: ['file'],
+ },
+ },
+ description: 'The ID of the file',
+ },
+
+ {
+ displayName: 'Folder',
+ name: 'fileId',
+ type: 'resourceLocator',
+ default: { mode: 'list', value: '' },
+ required: true,
+ modes: [
+ {
+ displayName: 'Folder',
+ name: 'list',
+ type: 'list',
+ placeholder: 'Select a folder...',
+ typeOptions: {
+ searchListMethod: 'folderSearch',
+ searchable: true,
+ },
+ },
+ {
+ displayName: 'Link',
+ name: 'url',
+ type: 'string',
+ placeholder: 'https://drive.google.com/drive/folders/1Tx9WHbA3wBpPB4C_HcoZDH9WZFWYxAMU',
+ extractValue: {
+ type: 'regex',
+ regex:
+ 'https:\\/\\/drive\\.google\\.com\\/\\w+\\/folders\\/([0-9a-zA-Z\\-_]+)(?:\\/.*|)',
+ },
+ validation: [
+ {
+ type: 'regex',
+ properties: {
+ regex:
+ 'https:\\/\\/drive\\.google\\.com\\/\\w+\\/folders\\/([0-9a-zA-Z\\-_]+)(?:\\/.*|)',
+ errorMessage: 'Not a valid Google Drive Folder URL',
+ },
+ },
+ ],
+ },
+ {
+ displayName: 'ID',
+ name: 'id',
+ type: 'string',
+ placeholder: '1anGBg0b5re2VtF2bKu201_a-Vnz5BHq9Y4r-yBDAj5A',
+ validation: [
+ {
+ type: 'regex',
+ properties: {
+ regex: '[a-zA-Z0-9\\-_]{2,}',
+ errorMessage: 'Not a valid Google Drive Folder ID',
+ },
+ },
+ ],
+ url: '=https://drive.google.com/drive/folders/{{$value}}',
+ },
+ ],
+ displayOptions: {
+ show: {
+ operation: ['delete', 'share'],
+ resource: ['folder'],
+ },
+ },
+ description: 'The ID of the folder',
+ },
+
+ // ----------------------------------
+ // file:copy
+ // ----------------------------------
+
+ // ----------------------------------
+ // file/folder:delete
+ // ----------------------------------
+
+ // ----------------------------------
+ // file:download
+ // ----------------------------------
+ {
+ displayName: 'Binary Property',
+ name: 'binaryPropertyName',
+ type: 'string',
+ required: true,
+ default: 'data',
+ displayOptions: {
+ show: {
+ operation: ['download'],
+ resource: ['file'],
+ },
+ },
+ description: 'Name of the binary property to which to write the data of the read file',
+ },
+ {
+ displayName: 'Options',
+ name: 'options',
+ type: 'collection',
+ placeholder: 'Add Option',
+ default: {},
+ displayOptions: {
+ show: {
+ operation: ['download'],
+ resource: ['file'],
+ },
+ },
+ options: [
+ {
+ displayName: 'Google File Conversion',
+ name: 'googleFileConversion',
+ type: 'fixedCollection',
+ typeOptions: {
+ multipleValues: false,
+ },
+ default: {},
+ placeholder: 'Add Conversion',
+ options: [
+ {
+ displayName: 'Conversion',
+ name: 'conversion',
+ values: [
+ {
+ displayName: 'Google Docs',
+ name: 'docsToFormat',
+ type: 'options',
+ options: [
+ {
+ name: 'To HTML',
+ value: 'text/html',
+ },
+ {
+ name: 'To MS Word',
+ value:
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
+ },
+ {
+ name: 'To OpenOffice Doc',
+ value: 'application/vnd.oasis.opendocument.text',
+ },
+ {
+ name: 'To PDF',
+ value: 'application/pdf',
+ },
+ {
+ name: 'To Rich Text',
+ value: 'application/rtf',
+ },
+ ],
+ default:
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
+ description: 'Format used to export when downloading Google Docs files',
+ },
+ {
+ displayName: 'Google Drawings',
+ name: 'drawingsToFormat',
+ type: 'options',
+ options: [
+ {
+ name: 'To JPEG',
+ value: 'image/jpeg',
+ },
+ {
+ name: 'To PNG',
+ value: 'image/png',
+ },
+ {
+ name: 'To SVG',
+ value: 'image/svg+xml',
+ },
+ {
+ name: 'To PDF',
+ value: 'application/pdf',
+ },
+ ],
+ default: 'image/jpeg',
+ description: 'Format used to export when downloading Google Drawings files',
+ },
+ {
+ displayName: 'Google Slides',
+ name: 'slidesToFormat',
+ type: 'options',
+ options: [
+ {
+ name: 'To MS PowerPoint',
+ value:
+ 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
+ },
+ {
+ name: 'To PDF',
+ value: 'application/pdf',
+ },
+ {
+ name: 'To OpenOffice Presentation',
+ value: 'application/vnd.oasis.opendocument.presentation',
+ },
+ {
+ name: 'To Plain Text',
+ value: 'text/plain',
+ },
+ ],
+ default:
+ 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
+ description: 'Format used to export when downloading Google Slides files',
+ },
+ {
+ displayName: 'Google Sheets',
+ name: 'sheetsToFormat',
+ type: 'options',
+ options: [
+ {
+ name: 'To MS Excel',
+ value: 'application/x-vnd.oasis.opendocument.spreadsheet',
+ },
+ {
+ name: 'To PDF',
+ value: 'application/pdf',
+ },
+ {
+ name: 'To CSV',
+ value: 'text/csv',
+ },
+ ],
+ default: 'application/x-vnd.oasis.opendocument.spreadsheet',
+ description: 'Format used to export when downloading Google Spreadsheets files',
+ },
+ ],
+ },
+ ],
+ },
+ {
+ displayName: 'File Name',
+ name: 'fileName',
+ type: 'string',
+ default: '',
+ description: 'File name. Ex: data.pdf.',
+ },
+ ],
+ },
+
+ // ----------------------------------
+ // file:list
+ // ----------------------------------
+ {
+ displayName: 'Use Query String',
+ name: 'useQueryString',
+ type: 'boolean',
+ default: false,
+ displayOptions: {
+ show: {
+ operation: ['list'],
+ resource: ['file'],
+ },
+ },
+ description: 'Whether a query string should be used to filter results',
+ },
+ {
+ displayName: 'Query String',
+ name: 'queryString',
+ type: 'string',
+ default: '',
+ displayOptions: {
+ show: {
+ operation: ['list'],
+ useQueryString: [true],
+ resource: ['file'],
+ },
+ },
+ placeholder: "name contains 'invoice'",
+ description: 'Query to use to return only specific files',
+ },
+ {
+ displayName: 'Limit',
+ name: 'limit',
+ type: 'number',
+ displayOptions: {
+ show: {
+ operation: ['list'],
+ resource: ['file'],
+ },
+ },
+ typeOptions: {
+ minValue: 1,
+ maxValue: 1000,
+ },
+ default: 50,
+ description: 'Max number of results to return',
+ },
+ {
+ displayName: 'Filters',
+ name: 'queryFilters',
+ placeholder: 'Add Filter',
+ description: 'Filters to use to return only specific files',
+ type: 'fixedCollection',
+ typeOptions: {
+ multipleValues: true,
+ },
+ default: {},
+ displayOptions: {
+ show: {
+ operation: ['list'],
+ useQueryString: [false],
+ resource: ['file'],
+ },
+ },
+ options: [
+ {
+ name: 'name',
+ displayName: 'Name',
+ values: [
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ noDataExpression: true,
+ options: [
+ {
+ name: 'Contains',
+ value: 'contains',
+ },
+ {
+ name: 'Is',
+ value: 'is',
+ },
+ {
+ name: 'Is Not',
+ value: 'isNot',
+ },
+ ],
+ default: 'contains',
+ },
+ {
+ displayName: 'Value',
+ name: 'value',
+ type: 'string',
+ default: '',
+ description: 'The value for operation',
+ },
+ ],
+ },
+ {
+ name: 'mimeType',
+ displayName: 'Mime Type',
+ values: [
+ {
+ displayName: 'Mime Type',
+ name: 'mimeType',
+ type: 'options',
+ options: [
+ {
+ name: '3rd Party Shortcut',
+ value: 'application/vnd.google-apps.drive-sdk',
+ },
+ {
+ name: 'Audio',
+ value: 'application/vnd.google-apps.audio',
+ },
+ {
+ name: 'Custom Mime Type',
+ value: 'custom',
+ },
+ {
+ name: 'Google Apps Scripts',
+ value: 'application/vnd.google-apps.script',
+ },
+ {
+ name: 'Google Docs',
+ value: 'application/vnd.google-apps.document',
+ },
+ {
+ name: 'Google Drawing',
+ value: 'application/vnd.google-apps.drawing',
+ },
+ {
+ name: 'Google Drive File',
+ value: 'application/vnd.google-apps.file',
+ },
+ {
+ name: 'Google Drive Folder',
+ value: 'application/vnd.google-apps.folder',
+ },
+ {
+ name: 'Google Forms',
+ value: 'application/vnd.google-apps.form',
+ },
+ {
+ name: 'Google Fusion Tables',
+ value: 'application/vnd.google-apps.fusiontable',
+ },
+ {
+ name: 'Google My Maps',
+ value: 'application/vnd.google-apps.map',
+ },
+ {
+ name: 'Google Sheets',
+ value: 'application/vnd.google-apps.spreadsheet',
+ },
+ {
+ name: 'Google Sites',
+ value: 'application/vnd.google-apps.site',
+ },
+ {
+ name: 'Google Slides',
+ value: 'application/vnd.google-apps.presentation',
+ },
+ {
+ name: 'Photo',
+ value: 'application/vnd.google-apps.photo',
+ },
+ {
+ name: 'Unknown',
+ value: 'application/vnd.google-apps.unknown',
+ },
+ {
+ name: 'Video',
+ value: 'application/vnd.google-apps.video',
+ },
+ ],
+ default: 'application/vnd.google-apps.file',
+ description: 'The Mime-Type of the files to return',
+ },
+ {
+ displayName: 'Custom Mime Type',
+ name: 'customMimeType',
+ type: 'string',
+ default: '',
+ displayOptions: {
+ show: {
+ mimeType: ['custom'],
+ },
+ },
+ },
+ ],
+ },
+ ],
+ },
+
+ // ----------------------------------
+ // file:share
+ // ----------------------------------
+ {
+ displayName: 'Permissions',
+ name: 'permissionsUi',
+ placeholder: 'Add Permission',
+ type: 'fixedCollection',
+ default: {},
+ typeOptions: {
+ multipleValues: false,
+ },
+ displayOptions: {
+ show: {
+ resource: ['file', 'folder'],
+ operation: ['share'],
+ },
+ },
+ options: [
+ {
+ displayName: 'Permission',
+ name: 'permissionsValues',
+ values: [
+ {
+ displayName: 'Role',
+ name: 'role',
+ type: 'options',
+ options: [
+ {
+ name: 'Commenter',
+ value: 'commenter',
+ },
+ {
+ name: 'File Organizer',
+ value: 'fileOrganizer',
+ },
+ {
+ name: 'Organizer',
+ value: 'organizer',
+ },
+ {
+ name: 'Owner',
+ value: 'owner',
+ },
+ {
+ name: 'Reader',
+ value: 'reader',
+ },
+ {
+ name: 'Writer',
+ value: 'writer',
+ },
+ ],
+ default: '',
+ },
+ {
+ displayName: 'Type',
+ name: 'type',
+ type: 'options',
+ options: [
+ {
+ name: 'User',
+ value: 'user',
+ },
+ {
+ name: 'Group',
+ value: 'group',
+ },
+ {
+ name: 'Domain',
+ value: 'domain',
+ },
+ {
+ name: 'Anyone',
+ value: 'anyone',
+ },
+ ],
+ default: '',
+ description:
+ 'Information about the different types can be found here',
+ },
+ {
+ displayName: 'Email Address',
+ name: 'emailAddress',
+ type: 'string',
+ displayOptions: {
+ show: {
+ type: ['user', 'group'],
+ },
+ },
+ default: '',
+ description: 'The email address of the user or group to which this permission refers',
+ },
+ {
+ displayName: 'Domain',
+ name: 'domain',
+ type: 'string',
+ displayOptions: {
+ show: {
+ type: ['domain'],
+ },
+ },
+ default: '',
+ description: 'The domain to which this permission refers',
+ },
+ {
+ displayName: 'Allow File Discovery',
+ name: 'allowFileDiscovery',
+ type: 'boolean',
+ displayOptions: {
+ show: {
+ type: ['domain', 'anyone'],
+ },
+ },
+ default: false,
+ description: 'Whether the permission allows the file to be discovered through search',
+ },
+ ],
+ },
+ ],
+ },
+
+ {
+ displayName: 'Binary Data',
+ name: 'binaryData',
+ type: 'boolean',
+ default: false,
+ displayOptions: {
+ show: {
+ operation: ['upload'],
+ resource: ['file'],
+ },
+ },
+ description: 'Whether the data to upload should be taken from binary field',
+ },
+ {
+ displayName: 'File Content',
+ name: 'fileContent',
+ type: 'string',
+ default: '',
+ displayOptions: {
+ show: {
+ operation: ['upload'],
+ resource: ['file'],
+ binaryData: [false],
+ },
+ },
+ placeholder: '',
+ description: 'The text content of the file to upload',
+ },
+ {
+ displayName: 'Binary Property',
+ name: 'binaryPropertyName',
+ type: 'string',
+ default: 'data',
+ required: true,
+ displayOptions: {
+ show: {
+ operation: ['upload'],
+ resource: ['file'],
+ binaryData: [true],
+ },
+ },
+ placeholder: '',
+ description:
+ 'Name of the binary property which contains the data for the file to be uploaded',
+ },
+
+ // ----------------------------------
+ // file:update
+ // ----------------------------------
+ {
+ displayName: 'Update Fields',
+ name: 'updateFields',
+ type: 'collection',
+ placeholder: 'Add Option',
+ default: {},
+ displayOptions: {
+ show: {
+ operation: ['update'],
+ resource: ['file'],
+ },
+ },
+ options: [
+ {
+ displayName: 'File Name',
+ name: 'fileName',
+ type: 'string',
+ default: '',
+ description: 'The name of the file',
+ },
+ {
+ displayName: 'Keep Revision Forever',
+ name: 'keepRevisionForever',
+ type: 'boolean',
+ default: false,
+ description:
+ "Whether to set the 'keepForever' field in the new head revision. This is only applicable to files with binary content in Google Drive. Only 200 revisions for the file can be kept forever. If the limit is reached, try deleting pinned revisions.",
+ },
+ {
+ displayName: 'Move to Trash',
+ name: 'trashed',
+ type: 'boolean',
+ default: false,
+ description: 'Whether to move a file to the trash. Only the owner may trash a file.',
+ },
+ {
+ displayName: 'OCR Language',
+ name: 'ocrLanguage',
+ type: 'string',
+ default: '',
+ description: 'A language hint for OCR processing during image import (ISO 639-1 code)',
+ },
+ {
+ displayName: 'Parent ID',
+ name: 'parentId',
+ type: 'string',
+ default: '',
+ description: 'The ID of the parent to set',
+ },
+ {
+ displayName: 'Use Content As Indexable Text',
+ name: 'useContentAsIndexableText',
+ type: 'boolean',
+ default: false,
+ description: 'Whether to use the uploaded content as indexable text',
+ },
+ ],
+ },
+ {
+ displayName: 'Options',
+ name: 'options',
+ type: 'collection',
+ placeholder: 'Add Option',
+ default: {},
+ displayOptions: {
+ show: {
+ operation: ['update'],
+ resource: ['file'],
+ },
+ },
+ options: [
+ {
+ displayName: 'Fields',
+ name: 'fields',
+ type: 'multiOptions',
+ options: [
+ {
+ name: '[All]',
+ value: '*',
+ description: 'All fields',
+ },
+ {
+ name: 'explicitlyTrashed',
+ value: 'explicitlyTrashed',
+ },
+ {
+ name: 'exportLinks',
+ value: 'exportLinks',
+ },
+ {
+ name: 'hasThumbnail',
+ value: 'hasThumbnail',
+ },
+ {
+ name: 'iconLink',
+ value: 'iconLink',
+ },
+ {
+ name: 'ID',
+ value: 'id',
+ },
+ {
+ name: 'Kind',
+ value: 'kind',
+ },
+ {
+ name: 'mimeType',
+ value: 'mimeType',
+ },
+ {
+ name: 'Name',
+ value: 'name',
+ },
+ {
+ name: 'Permissions',
+ value: 'permissions',
+ },
+ {
+ name: 'Shared',
+ value: 'shared',
+ },
+ {
+ name: 'Spaces',
+ value: 'spaces',
+ },
+ {
+ name: 'Starred',
+ value: 'starred',
+ },
+ {
+ name: 'thumbnailLink',
+ value: 'thumbnailLink',
+ },
+ {
+ name: 'Trashed',
+ value: 'trashed',
+ },
+ {
+ name: 'Version',
+ value: 'version',
+ },
+ {
+ name: 'webViewLink',
+ value: 'webViewLink',
+ },
+ ],
+ default: [],
+ description: 'The fields to return',
+ },
+ ],
+ },
+ // ----------------------------------
+ // file:upload
+ // ----------------------------------
+ {
+ displayName: 'File Name',
+ name: 'name',
+ type: 'string',
+ default: '',
+ required: true,
+ displayOptions: {
+ show: {
+ operation: ['upload'],
+ resource: ['file'],
+ },
+ },
+ placeholder: 'invoice_1.pdf',
+ description: 'The name the file should be saved as',
+ },
+ // ----------------------------------
+ {
+ displayName: 'Resolve Data',
+ name: 'resolveData',
+ type: 'boolean',
+ default: false,
+ displayOptions: {
+ show: {
+ operation: ['upload'],
+ resource: ['file'],
+ },
+ },
+ // eslint-disable-next-line n8n-nodes-base/node-param-description-boolean-without-whether
+ description:
+ 'By default the response only contain the ID of the file. If this option gets activated, it will resolve the data automatically.',
+ },
+ {
+ displayName: 'Parents',
+ name: 'parents',
+ type: 'string',
+ typeOptions: {
+ multipleValues: true,
+ },
+ default: [],
+ displayOptions: {
+ show: {
+ operation: ['upload'],
+ resource: ['file'],
+ },
+ },
+ description: 'The IDs of the parent folders which contain the file',
+ },
+
+ // ----------------------------------
+ // folder
+ // ----------------------------------
+
+ // ----------------------------------
+ // folder:create
+ // ----------------------------------
+ {
+ displayName: 'Folder',
+ name: 'name',
+ type: 'string',
+ default: '',
+ required: true,
+ displayOptions: {
+ show: {
+ operation: ['create'],
+ resource: ['folder'],
+ },
+ },
+ placeholder: 'invoices',
+ description: 'The name of folder to create',
+ },
+ {
+ displayName: 'Options',
+ name: 'options',
+ type: 'collection',
+ placeholder: 'Add Option',
+ default: {},
+ displayOptions: {
+ show: {
+ '/operation': ['copy', 'list', 'share', 'create'],
+ '/resource': ['file', 'folder'],
+ },
+ },
+ options: [
+ {
+ displayName: 'Email Message',
+ name: 'emailMessage',
+ type: 'string',
+ displayOptions: {
+ show: {
+ '/operation': ['share'],
+ '/resource': ['file', 'folder'],
+ },
+ },
+ default: '',
+ description: 'A plain text custom message to include in the notification email',
+ },
+ {
+ displayName: 'Enforce Single Parent',
+ name: 'enforceSingleParent',
+ type: 'boolean',
+ displayOptions: {
+ show: {
+ '/operation': ['share'],
+ '/resource': ['file', 'folder'],
+ },
+ },
+ default: false,
+ description:
+ 'Whether to opt in to API behavior that aims for all items to have exactly one parent. This parameter only takes effect if the item is not in a shared drive.',
+ },
+ {
+ displayName: 'Fields',
+ name: 'fields',
+ type: 'multiOptions',
+ displayOptions: {
+ show: {
+ '/operation': ['list', 'copy'],
+ },
+ },
+ options: [
+ {
+ name: '*',
+ value: '*',
+ description: 'All fields',
+ },
+ {
+ name: 'explicitlyTrashed',
+ value: 'explicitlyTrashed',
+ },
+ {
+ name: 'exportLinks',
+ value: 'exportLinks',
+ },
+ {
+ name: 'hasThumbnail',
+ value: 'hasThumbnail',
+ },
+ {
+ name: 'iconLink',
+ value: 'iconLink',
+ },
+ {
+ name: 'ID',
+ value: 'id',
+ },
+ {
+ name: 'Kind',
+ value: 'kind',
+ },
+ {
+ name: 'mimeType',
+ value: 'mimeType',
+ },
+ {
+ name: 'Name',
+ value: 'name',
+ },
+ {
+ name: 'Permissions',
+ value: 'permissions',
+ },
+ {
+ name: 'Shared',
+ value: 'shared',
+ },
+ {
+ name: 'Spaces',
+ value: 'spaces',
+ },
+ {
+ name: 'Starred',
+ value: 'starred',
+ },
+ {
+ name: 'thumbnailLink',
+ value: 'thumbnailLink',
+ },
+ {
+ name: 'Trashed',
+ value: 'trashed',
+ },
+ {
+ name: 'Version',
+ value: 'version',
+ },
+ {
+ name: 'webViewLink',
+ value: 'webViewLink',
+ },
+ ],
+ default: [],
+ description: 'The fields to return',
+ },
+ {
+ displayName: 'Move To New Owners Root',
+ name: 'moveToNewOwnersRoot',
+ type: 'boolean',
+ displayOptions: {
+ show: {
+ '/operation': ['share'],
+ '/resource': ['file', 'folder'],
+ },
+ },
+ default: false,
+ // eslint-disable-next-line n8n-nodes-base/node-param-description-boolean-without-whether
+ description:
+ "This parameter only takes effect if the item is not in a shared drive and the request is attempting to transfer the ownership of the item.
When set to true, the item is moved to the new owner's My Drive root folder and all prior parents removed.
",
+ },
+ {
+ displayName: 'Send Notification Email',
+ name: 'sendNotificationEmail',
+ type: 'boolean',
+ displayOptions: {
+ show: {
+ '/operation': ['share'],
+ '/resource': ['file', 'folder'],
+ },
+ },
+ default: false,
+ description: 'Whether to send a notification email when sharing to users or groups',
+ },
+ {
+ displayName: 'Supports All Drives',
+ name: 'supportsAllDrives',
+ type: 'boolean',
+ displayOptions: {
+ show: {
+ '/operation': ['share'],
+ '/resource': ['file', 'folder'],
+ },
+ },
+ default: false,
+ description:
+ 'Whether the requesting application supports both My Drives and shared drives',
+ },
+ {
+ displayName: 'Transfer Ownership',
+ name: 'transferOwnership',
+ type: 'boolean',
+ displayOptions: {
+ show: {
+ '/operation': ['share'],
+ '/resource': ['file', 'folder'],
+ },
+ },
+ default: false,
+ description:
+ 'Whether to transfer ownership to the specified user and downgrade the current owner to a writer',
+ },
+ {
+ displayName: 'Use Domain Admin Access',
+ name: 'useDomainAdminAccess',
+ type: 'boolean',
+ displayOptions: {
+ show: {
+ '/operation': ['share'],
+ '/resource': ['file', 'folder'],
+ },
+ },
+ default: false,
+ description:
+ 'Whether to perform the operation as domain administrator, i.e. if you are an administrator of the domain to which the shared drive belongs, you will be granted access automatically.',
+ },
+
+ {
+ displayName: 'File Name',
+ name: 'name',
+ type: 'string',
+ displayOptions: {
+ show: {
+ '/operation': ['copy'],
+ '/resource': ['file'],
+ },
+ },
+ default: '',
+ placeholder: 'invoice_1.pdf',
+ description: 'The name the file should be saved as',
+ },
+ {
+ displayName: 'Parents',
+ name: 'parents',
+ type: 'string',
+ displayOptions: {
+ show: {
+ '/operation': ['copy', 'create'],
+ '/resource': ['file', 'folder'],
+ },
+ },
+ typeOptions: {
+ multipleValues: true,
+ },
+ default: [],
+ description: 'The IDs of the parent folders the file/folder should be saved in',
+ },
+ {
+ displayName: 'Spaces',
+ name: 'spaces',
+ type: 'multiOptions',
+ displayOptions: {
+ show: {
+ '/operation': ['list'],
+ '/resource': ['file'],
+ },
+ },
+ options: [
+ {
+ name: '[All]',
+ value: '*',
+ description: 'All spaces',
+ },
+ {
+ name: 'appDataFolder',
+ value: 'appDataFolder',
+ },
+ {
+ name: 'Drive',
+ value: 'drive',
+ },
+ {
+ name: 'Photos',
+ value: 'photos',
+ },
+ ],
+ default: [],
+ description: 'The spaces to operate on',
+ },
+ {
+ displayName: 'Corpora',
+ name: 'corpora',
+ type: 'options',
+ displayOptions: {
+ show: {
+ '/operation': ['list'],
+ '/resource': ['file'],
+ },
+ },
+ options: [
+ {
+ name: 'User',
+ value: 'user',
+ description: 'All files in "My Drive" and "Shared with me"',
+ },
+ {
+ name: 'Domain',
+ value: 'domain',
+ description: "All files shared to the user's domain that are searchable",
+ },
+ {
+ name: 'Drive',
+ value: 'drive',
+ description: 'All files contained in a single shared drive',
+ },
+ {
+ name: 'allDrives',
+ value: 'allDrives',
+ description: 'All drives',
+ },
+ ],
+ default: '',
+ description: 'The corpora to operate on',
+ },
+ {
+ displayName: 'Drive ID',
+ name: 'driveId',
+ type: 'string',
+ default: '',
+ displayOptions: {
+ show: {
+ '/operation': ['list'],
+ '/resource': ['file'],
+ corpora: ['drive'],
+ },
+ },
+ description:
+ 'ID of the shared drive to search. The driveId parameter must be specified if and only if corpora is set to drive.',
+ },
+ ],
+ },
+ // ----------------------------------
+ // drive
+ // ----------------------------------
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ noDataExpression: true,
+ displayOptions: {
+ show: {
+ resource: ['drive'],
+ },
+ },
+ options: [
+ {
+ name: 'Create',
+ value: 'create',
+ description: 'Create a drive',
+ action: 'Create a drive',
+ },
+ {
+ name: 'Delete',
+ value: 'delete',
+ description: 'Delete a drive',
+ action: 'Delete a drive',
+ },
+ {
+ name: 'Get',
+ value: 'get',
+ description: 'Get a drive',
+ action: 'Get a drive',
+ },
+ {
+ name: 'List',
+ value: 'list',
+ description: 'List all drives',
+ action: 'List all drives',
+ },
+ {
+ name: 'Update',
+ value: 'update',
+ description: 'Update a drive',
+ action: 'Update a drive',
+ },
+ ],
+ default: 'create',
+ },
+
+ {
+ displayName: 'Drive',
+ name: 'driveId',
+ type: 'resourceLocator',
+ default: { mode: 'list', value: '' },
+ required: true,
+ hint: 'The Google Drive drive to operate on',
+ modes: [
+ {
+ displayName: 'Drive',
+ name: 'list',
+ type: 'list',
+ placeholder: 'Drive',
+ typeOptions: {
+ searchListMethod: 'driveSearch',
+ searchable: true,
+ },
+ },
+ {
+ displayName: 'Link',
+ name: 'url',
+ type: 'string',
+ placeholder: 'https://drive.google.com/drive/folders/0AaaaaAAAAAAAaa',
+ extractValue: {
+ type: 'regex',
+ regex:
+ 'https:\\/\\/drive\\.google\\.com\\/\\w+\\/folders\\/([0-9a-zA-Z\\-_]+)(?:\\/.*|)',
+ },
+ validation: [
+ {
+ type: 'regex',
+ properties: {
+ regex:
+ 'https:\\/\\/drive\\.google\\.com\\/\\w+\\/folders\\/([0-9a-zA-Z\\-_]+)(?:\\/.*|)',
+ errorMessage: 'Not a valid Google Drive Drive URL',
+ },
+ },
+ ],
+ },
+ {
+ displayName: 'ID',
+ name: 'id',
+ type: 'string',
+ hint: 'The ID of the shared drive',
+ validation: [
+ {
+ type: 'regex',
+ properties: {
+ regex: '[a-zA-Z0-9\\-_]{2,}',
+ errorMessage: 'Not a valid Google Drive Drive ID',
+ },
+ },
+ ],
+ url: '=https://drive.google.com/drive/folders/{{$value}}',
+ },
+ ],
+ displayOptions: {
+ show: {
+ operation: ['delete', 'get', 'update'],
+ resource: ['drive'],
+ },
+ },
+ description: 'The ID of the drive',
+ },
+
+ // ----------------------------------
+ // drive:create
+ // ----------------------------------
+ {
+ displayName: 'Name',
+ name: 'name',
+ type: 'string',
+ default: '',
+ displayOptions: {
+ show: {
+ operation: ['create'],
+ resource: ['drive'],
+ },
+ },
+ description: 'The name of this shared drive',
+ },
+ {
+ displayName: 'Options',
+ name: 'options',
+ type: 'collection',
+ placeholder: 'Add Option',
+ default: {},
+ displayOptions: {
+ show: {
+ operation: ['create'],
+ resource: ['drive'],
+ },
+ },
+ options: [
+ {
+ displayName: 'Capabilities',
+ name: 'capabilities',
+ type: 'collection',
+ placeholder: 'Add Field',
+ default: {},
+ options: [
+ {
+ displayName: 'Can Add Children',
+ name: 'canAddChildren',
+ type: 'boolean',
+ default: false,
+ description:
+ 'Whether the current user can add children to folders in this shared drive',
+ },
+ {
+ displayName: 'Can Change Copy Requires Writer Permission Restriction',
+ name: 'canChangeCopyRequiresWriterPermissionRestriction',
+ type: 'boolean',
+ default: false,
+ description:
+ 'Whether the current user can change the copyRequiresWriterPermission restriction of this shared drive',
+ },
+ {
+ displayName: 'Can Change Domain Users Only Restriction',
+ name: 'canChangeDomainUsersOnlyRestriction',
+ type: 'boolean',
+ default: false,
+ description:
+ 'Whether the current user can change the domainUsersOnly restriction of this shared drive',
+ },
+ {
+ displayName: 'Can Change Drive Background',
+ name: 'canChangeDriveBackground',
+ type: 'boolean',
+ default: false,
+ description:
+ 'Whether the current user can change the background of this shared drive',
+ },
+ {
+ displayName: 'Can Change Drive Members Only Restriction',
+ name: 'canChangeDriveMembersOnlyRestriction',
+ type: 'boolean',
+ default: false,
+ description:
+ 'Whether the current user can change the driveMembersOnly restriction of this shared drive',
+ },
+ {
+ displayName: 'Can Comment',
+ name: 'canComment',
+ type: 'boolean',
+ default: false,
+ description: 'Whether the current user can comment on files in this shared drive',
+ },
+ {
+ displayName: 'Can Copy',
+ name: 'canCopy',
+ type: 'boolean',
+ default: false,
+ description: 'Whether the current user can copy files in this shared drive',
+ },
+ {
+ displayName: 'Can Delete Children',
+ name: 'canDeleteChildren',
+ type: 'boolean',
+ default: false,
+ description:
+ 'Whether the current user can delete children from folders in this shared drive',
+ },
+ {
+ displayName: 'Can Delete Drive',
+ name: 'canDeleteDrive',
+ type: 'boolean',
+ default: false,
+ description:
+ 'Whether the current user can delete this shared drive. Attempting to delete the shared drive may still fail if there are untrashed items inside the shared drive.',
+ },
+ {
+ displayName: 'Can Download',
+ name: 'canDownload',
+ type: 'boolean',
+ default: false,
+ description: 'Whether the current user can download files in this shared drive',
+ },
+ {
+ displayName: 'Can Edit',
+ name: 'canEdit',
+ type: 'boolean',
+ default: false,
+ description: 'Whether the current user can edit files in this shared drive',
+ },
+ {
+ displayName: 'Can List Children',
+ name: 'canListChildren',
+ type: 'boolean',
+ default: false,
+ description:
+ 'Whether the current user can list the children of folders in this shared drive',
+ },
+ {
+ displayName: 'Can Manage Members',
+ name: 'canManageMembers',
+ type: 'boolean',
+ default: false,
+ description:
+ 'Whether the current user can add members to this shared drive or remove them or change their role',
+ },
+ {
+ displayName: 'Can Read Revisions',
+ name: 'canReadRevisions',
+ type: 'boolean',
+ default: false,
+ description:
+ 'Whether the current user can read the revisions resource of files in this shared drive',
+ },
+ {
+ displayName: 'Can Rename',
+ name: 'canRename',
+ type: 'boolean',
+ default: false,
+ description:
+ 'Whether the current user can rename files or folders in this shared drive',
+ },
+ {
+ displayName: 'Can Rename Drive',
+ name: 'canRenameDrive',
+ type: 'boolean',
+ default: false,
+ description: 'Whether the current user can rename this shared drive',
+ },
+ {
+ displayName: 'Can Share',
+ name: 'canShare',
+ type: 'boolean',
+ default: false,
+ description: 'Whether the current user can rename this shared drive',
+ },
+ {
+ displayName: 'Can Trash Children',
+ name: 'canTrashChildren',
+ type: 'boolean',
+ default: false,
+ description:
+ 'Whether the current user can trash children from folders in this shared drive',
+ },
+ ],
+ },
+ {
+ displayName: 'Color RGB',
+ name: 'colorRgb',
+ type: 'color',
+ default: '',
+ description: 'The color of this shared drive as an RGB hex string',
+ },
+ {
+ displayName: 'Created Time',
+ name: 'createdTime',
+ type: 'dateTime',
+ default: '',
+ description: 'The time at which the shared drive was created (RFC 3339 date-time)',
+ },
+ {
+ displayName: 'Hidden',
+ name: 'hidden',
+ type: 'boolean',
+ default: false,
+ description: 'Whether the shared drive is hidden from default view',
+ },
+ {
+ displayName: 'Restrictions',
+ name: 'restrictions',
+ type: 'collection',
+ placeholder: 'Add Field',
+ default: {},
+ options: [
+ {
+ displayName: 'Admin Managed Restrictions',
+ name: 'adminManagedRestrictions',
+ type: 'boolean',
+ default: false,
+ description:
+ 'Whether the options to copy, print, or download files inside this shared drive, should be disabled for readers and commenters. When this restriction is set to true, it will override the similarly named field to true for any file inside this shared drive.',
+ },
+ {
+ displayName: 'Copy Requires Writer Permission',
+ name: 'copyRequiresWriterPermission',
+ type: 'boolean',
+ default: false,
+ description:
+ 'Whether the options to copy, print, or download files inside this shared drive, should be disabled for readers and commenters. When this restriction is set to true, it will override the similarly named field to true for any file inside this shared drive.',
+ },
+ {
+ displayName: 'Domain Users Only',
+ name: 'domainUsersOnly',
+ type: 'boolean',
+ default: false,
+ description:
+ 'Whether access to this shared drive and items inside this shared drive is restricted to users of the domain to which this shared drive belongs. This restriction may be overridden by other sharing policies controlled outside of this shared drive.',
+ },
+ {
+ displayName: 'Drive Members Only',
+ name: 'driveMembersOnly',
+ type: 'boolean',
+ default: false,
+ description:
+ 'Whether access to items inside this shared drive is restricted to its members',
+ },
+ ],
+ },
+ ],
+ },
+ // ----------------------------------
+ // drive:delete
+ // ----------------------------------
+
+ // ----------------------------------
+ // drive:get
+ // ----------------------------------
+ {
+ displayName: 'Options',
+ name: 'options',
+ type: 'collection',
+ placeholder: 'Add Option',
+ default: {},
+ displayOptions: {
+ show: {
+ operation: ['get'],
+ resource: ['drive'],
+ },
+ },
+ options: [
+ {
+ displayName: 'Use Domain Admin Access',
+ name: 'useDomainAdminAccess',
+ type: 'boolean',
+ default: false,
+ description:
+ 'Whether to issue the request as a domain administrator; if set to true, then the requester will be granted access if they are an administrator of the domain to which the shared drive belongs. (Default: false).',
+ },
+ ],
+ },
+ // ----------------------------------
+ // drive:list
+ // ----------------------------------
+ {
+ displayName: 'Return All',
+ name: 'returnAll',
+ type: 'boolean',
+ displayOptions: {
+ show: {
+ operation: ['list'],
+ resource: ['drive'],
+ },
+ },
+ default: false,
+ description: 'Whether to return all results or only up to a given limit',
+ },
+ {
+ displayName: 'Limit',
+ name: 'limit',
+ type: 'number',
+ displayOptions: {
+ show: {
+ operation: ['list'],
+ resource: ['drive'],
+ returnAll: [false],
+ },
+ },
+ typeOptions: {
+ minValue: 1,
+ maxValue: 200,
+ },
+ default: 100,
+ description: 'Max number of results to return',
+ },
+ {
+ displayName: 'Options',
+ name: 'options',
+ type: 'collection',
+ placeholder: 'Add Option',
+ default: {},
+ displayOptions: {
+ show: {
+ operation: ['list'],
+ resource: ['drive'],
+ },
+ },
+ options: [
+ {
+ displayName: 'Query',
+ name: 'q',
+ type: 'string',
+ default: '',
+ description:
+ 'Query string for searching shared drives. See the "Search for shared drives" guide for supported syntax.',
+ },
+ {
+ displayName: 'Use Domain Admin Access',
+ name: 'useDomainAdminAccess',
+ type: 'boolean',
+ default: false,
+ description:
+ 'Whether to issue the request as a domain administrator; if set to true, then the requester will be granted access if they are an administrator of the domain to which the shared drive belongs. (Default: false).',
+ },
+ ],
+ },
+ // ----------------------------------
+ // drive:update
+ // ----------------------------------
+ {
+ displayName: 'Update Fields',
+ name: 'options',
+ type: 'collection',
+ placeholder: 'Add Option',
+ default: {},
+ displayOptions: {
+ show: {
+ operation: ['update'],
+ resource: ['drive'],
+ },
+ },
+ options: [
+ {
+ displayName: 'Color RGB',
+ name: 'colorRgb',
+ type: 'color',
+ default: '',
+ description: 'The color of this shared drive as an RGB hex string',
+ },
+ {
+ displayName: 'Name',
+ name: 'name',
+ type: 'string',
+ default: '',
+ description: 'The name of this shared drive',
+ },
+ {
+ displayName: 'Restrictions',
+ name: 'restrictions',
+ type: 'collection',
+ placeholder: 'Add Field',
+ default: {},
+ options: [
+ {
+ displayName: 'Admin Managed Restrictions',
+ name: 'adminManagedRestrictions',
+ type: 'boolean',
+ default: false,
+ description:
+ 'Whether the options to copy, print, or download files inside this shared drive, should be disabled for readers and commenters. When this restriction is set to true, it will override the similarly named field to true for any file inside this shared drive.',
+ },
+ {
+ displayName: 'Copy Requires Writer Permission',
+ name: 'copyRequiresWriterPermission',
+ type: 'boolean',
+ default: false,
+ description:
+ 'Whether the options to copy, print, or download files inside this shared drive, should be disabled for readers and commenters. When this restriction is set to true, it will override the similarly named field to true for any file inside this shared drive.',
+ },
+ {
+ displayName: 'Domain Users Only',
+ name: 'domainUsersOnly',
+ type: 'boolean',
+ default: false,
+ description:
+ 'Whether access to this shared drive and items inside this shared drive is restricted to users of the domain to which this shared drive belongs. This restriction may be overridden by other sharing policies controlled outside of this shared drive.',
+ },
+ {
+ displayName: 'Drive Members Only',
+ name: 'driveMembersOnly',
+ type: 'boolean',
+ default: false,
+ description:
+ 'Whether access to items inside this shared drive is restricted to its members',
+ },
+ ],
+ },
+ ],
+ },
+ {
+ displayName: 'Options',
+ name: 'options',
+ type: 'collection',
+ placeholder: 'Add Option',
+ default: {},
+ displayOptions: {
+ show: {
+ operation: ['upload'],
+ resource: ['file'],
+ },
+ },
+ options: [
+ {
+ displayName: 'APP Properties',
+ name: 'appPropertiesUi',
+ placeholder: 'Add Property',
+ type: 'fixedCollection',
+ default: {},
+ typeOptions: {
+ multipleValues: true,
+ },
+ description:
+ 'A collection of arbitrary key-value pairs which are private to the requesting app',
+ options: [
+ {
+ name: 'appPropertyValues',
+ displayName: 'APP Property',
+ values: [
+ {
+ displayName: 'Key',
+ name: 'key',
+ type: 'string',
+ default: '',
+ description: 'Name of the key to add',
+ },
+ {
+ displayName: 'Value',
+ name: 'value',
+ type: 'string',
+ default: '',
+ description: 'Value to set for the key',
+ },
+ ],
+ },
+ ],
+ },
+ {
+ displayName: 'Properties',
+ name: 'propertiesUi',
+ placeholder: 'Add Property',
+ type: 'fixedCollection',
+ default: {},
+ typeOptions: {
+ multipleValues: true,
+ },
+ description: 'A collection of arbitrary key-value pairs which are visible to all apps',
+ options: [
+ {
+ name: 'propertyValues',
+ displayName: 'Property',
+ values: [
+ {
+ displayName: 'Key',
+ name: 'key',
+ type: 'string',
+ default: '',
+ description: 'Name of the key to add',
+ },
+ {
+ displayName: 'Value',
+ name: 'value',
+ type: 'string',
+ default: '',
+ description: 'Value to set for the key',
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+};
+
+export class GoogleDriveV1 implements INodeType {
+ description: INodeTypeDescription;
+
+ constructor(baseDescription: INodeTypeBaseDescription) {
+ this.description = {
+ ...baseDescription,
+ ...versionDescription,
+ };
+ }
+
+ methods = {
+ listSearch: {
+ fileSearch,
+ folderSearch,
+ driveSearch,
+ },
+ };
+
+ async execute(this: IExecuteFunctions): Promise {
+ const items = this.getInputData();
+ const returnData: INodeExecutionData[] = [];
+
+ const resource = this.getNodeParameter('resource', 0);
+ const operation = this.getNodeParameter('operation', 0);
+
+ for (let i = 0; i < items.length; i++) {
+ try {
+ const options = this.getNodeParameter('options', i, {});
+
+ let queryFields = 'id, name';
+ if (options?.fields) {
+ const fields = options.fields as string[];
+ if (fields.includes('*')) {
+ queryFields = '*';
+ } else {
+ queryFields = fields.join(', ');
+ }
+ }
+
+ if (resource === 'drive') {
+ if (operation === 'create') {
+ // ----------------------------------
+ // create
+ // ----------------------------------
+
+ const name = this.getNodeParameter('name', i) as string;
+
+ const body: IDataObject = {
+ name,
+ };
+
+ Object.assign(body, options);
+
+ const response = await googleApiRequest.call(this, 'POST', '/drive/v3/drives', body, {
+ requestId: uuid(),
+ });
+
+ const executionData = this.helpers.constructExecutionMetaData(
+ this.helpers.returnJsonArray(response as IDataObject[]),
+ { itemData: { item: i } },
+ );
+
+ returnData.push(...executionData);
+ }
+ if (operation === 'delete') {
+ // ----------------------------------
+ // delete
+ // ----------------------------------
+
+ const driveId = this.getNodeParameter('driveId', i, undefined, {
+ extractValue: true,
+ }) as string;
+
+ await googleApiRequest.call(this, 'DELETE', `/drive/v3/drives/${driveId}`);
+
+ const executionData = this.helpers.constructExecutionMetaData(
+ this.helpers.returnJsonArray({ success: true }),
+ { itemData: { item: i } },
+ );
+
+ returnData.push(...executionData);
+ }
+ if (operation === 'get') {
+ // ----------------------------------
+ // get
+ // ----------------------------------
+
+ const driveId = this.getNodeParameter('driveId', i, undefined, {
+ extractValue: true,
+ }) as string;
+
+ const qs: IDataObject = {};
+
+ Object.assign(qs, options);
+
+ const response = await googleApiRequest.call(
+ this,
+ 'GET',
+ `/drive/v3/drives/${driveId}`,
+ {},
+ qs,
+ );
+
+ const executionData = this.helpers.constructExecutionMetaData(
+ this.helpers.returnJsonArray(response as IDataObject[]),
+ { itemData: { item: i } },
+ );
+
+ returnData.push(...executionData);
+ }
+ if (operation === 'list') {
+ // ----------------------------------
+ // list
+ // ----------------------------------
+ const returnAll = this.getNodeParameter('returnAll', i);
+
+ const qs: IDataObject = {};
+
+ let response: IDataObject[] = [];
+
+ Object.assign(qs, options);
+
+ if (returnAll) {
+ response = await googleApiRequestAllItems.call(
+ this,
+ 'drives',
+ 'GET',
+ '/drive/v3/drives',
+ {},
+ qs,
+ );
+ } else {
+ qs.pageSize = this.getNodeParameter('limit', i);
+ const data = await googleApiRequest.call(this, 'GET', '/drive/v3/drives', {}, qs);
+ response = data.drives as IDataObject[];
+ }
+
+ const executionData = this.helpers.constructExecutionMetaData(
+ this.helpers.returnJsonArray(response),
+ { itemData: { item: i } },
+ );
+
+ returnData.push(...executionData);
+ }
+ if (operation === 'update') {
+ // ----------------------------------
+ // update
+ // ----------------------------------
+
+ const driveId = this.getNodeParameter('driveId', i, undefined, {
+ extractValue: true,
+ }) as string;
+
+ const body: IDataObject = {};
+
+ Object.assign(body, options);
+
+ const response = await googleApiRequest.call(
+ this,
+ 'PATCH',
+ `/drive/v3/drives/${driveId}`,
+ body,
+ );
+
+ const executionData = this.helpers.constructExecutionMetaData(
+ this.helpers.returnJsonArray(response as IDataObject[]),
+ { itemData: { item: i } },
+ );
+
+ returnData.push(...executionData);
+ }
+ }
+ if (resource === 'file') {
+ if (operation === 'copy') {
+ // ----------------------------------
+ // copy
+ // ----------------------------------
+
+ const fileId = this.getNodeParameter('fileId', i, undefined, {
+ extractValue: true,
+ }) as string;
+
+ const body: IDataObject = {
+ fields: queryFields,
+ };
+
+ const optionProperties = ['name', 'parents'];
+ for (const propertyName of optionProperties) {
+ if (options[propertyName] !== undefined) {
+ body[propertyName] = options[propertyName];
+ }
+ }
+
+ const qs = {
+ supportsAllDrives: true,
+ };
+
+ const response = await googleApiRequest.call(
+ this,
+ 'POST',
+ `/drive/v3/files/${fileId}/copy`,
+ body,
+ qs,
+ );
+
+ const executionData = this.helpers.constructExecutionMetaData(
+ this.helpers.returnJsonArray(response as IDataObject[]),
+ { itemData: { item: i } },
+ );
+
+ returnData.push(...executionData);
+ } else if (operation === 'download') {
+ // ----------------------------------
+ // download
+ // ----------------------------------
+
+ const fileId = this.getNodeParameter('fileId', i, undefined, {
+ extractValue: true,
+ }) as string;
+ const downloadOptions = this.getNodeParameter('options', i);
+
+ const requestOptions = {
+ useStream: true,
+ resolveWithFullResponse: true,
+ encoding: null,
+ json: false,
+ };
+
+ const file = await googleApiRequest.call(
+ this,
+ 'GET',
+ `/drive/v3/files/${fileId}`,
+ {},
+ { fields: 'mimeType,name', supportsTeamDrives: true },
+ );
+ let response;
+
+ if (file.mimeType.includes('vnd.google-apps')) {
+ const parameterKey = 'options.googleFileConversion.conversion';
+ const type = file.mimeType.split('.')[2];
+ let mime;
+ if (type === 'document') {
+ mime = this.getNodeParameter(
+ `${parameterKey}.docsToFormat`,
+ i,
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
+ ) as string;
+ } else if (type === 'presentation') {
+ mime = this.getNodeParameter(
+ `${parameterKey}.slidesToFormat`,
+ i,
+ 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
+ ) as string;
+ } else if (type === 'spreadsheet') {
+ mime = this.getNodeParameter(
+ `${parameterKey}.sheetsToFormat`,
+ i,
+ 'application/x-vnd.oasis.opendocument.spreadsheet',
+ ) as string;
+ } else {
+ mime = this.getNodeParameter(
+ `${parameterKey}.drawingsToFormat`,
+ i,
+ 'image/jpeg',
+ ) as string;
+ }
+ response = await googleApiRequest.call(
+ this,
+ 'GET',
+ `/drive/v3/files/${fileId}/export`,
+ {},
+ { mimeType: mime },
+ undefined,
+ requestOptions,
+ );
+ } else {
+ response = await googleApiRequest.call(
+ this,
+ 'GET',
+ `/drive/v3/files/${fileId}`,
+ {},
+ { alt: 'media' },
+ undefined,
+ requestOptions,
+ );
+ }
+
+ const mimeType = response.headers['content-type'] ?? file.mimeType ?? undefined;
+ const fileName = downloadOptions.fileName ?? file.name ?? undefined;
+
+ const newItem: INodeExecutionData = {
+ json: items[i].json,
+ binary: {},
+ };
+
+ if (items[i].binary !== undefined) {
+ // Create a shallow copy of the binary data so that the old
+ // data references which do not get changed still stay behind
+ // but the incoming data does not get changed.
+ Object.assign(newItem.binary as IBinaryKeyData, items[i].binary);
+ }
+
+ items[i] = newItem;
+
+ const dataPropertyNameDownload = this.getNodeParameter('binaryPropertyName', i);
+
+ items[i].binary![dataPropertyNameDownload] = await this.helpers.prepareBinaryData(
+ response.body as unknown as Readable,
+ fileName as string,
+ mimeType as string,
+ );
+ } else if (operation === 'list') {
+ // ----------------------------------
+ // list
+ // ----------------------------------
+
+ let querySpaces = '';
+ if (options.spaces) {
+ const spaces = options.spaces as string[];
+ if (spaces.includes('*')) {
+ querySpaces = 'appDataFolder, drive, photos';
+ } else {
+ querySpaces = spaces.join(', ');
+ }
+ }
+
+ let queryCorpora = '';
+ if (options.corpora) {
+ queryCorpora = options.corpora as string;
+ }
+
+ let driveId: string | undefined;
+ driveId = options.driveId as string;
+ if (driveId === '') {
+ driveId = undefined;
+ }
+
+ let queryString = '';
+ const useQueryString = this.getNodeParameter('useQueryString', i) as boolean;
+ if (useQueryString) {
+ // Use the user defined query string
+ queryString = this.getNodeParameter('queryString', i) as string;
+ } else {
+ // Build query string out of parameters set by user
+ const queryFilters = this.getNodeParameter('queryFilters', i) as IDataObject;
+
+ const queryFilterFields: string[] = [];
+ if (queryFilters.name) {
+ (queryFilters.name as IDataObject[]).forEach((nameFilter) => {
+ let filterOperation = nameFilter.operation;
+ if (filterOperation === 'is') {
+ filterOperation = '=';
+ } else if (filterOperation === 'isNot') {
+ filterOperation = '!=';
+ }
+ queryFilterFields.push(`name ${filterOperation} '${nameFilter.value}'`);
+ });
+
+ queryString += queryFilterFields.join(' or ');
+ }
+
+ queryFilterFields.length = 0;
+ if (queryFilters.mimeType) {
+ (queryFilters.mimeType as IDataObject[]).forEach((mimeTypeFilter) => {
+ let mimeType = mimeTypeFilter.mimeType;
+ if (mimeTypeFilter.mimeType === 'custom') {
+ mimeType = mimeTypeFilter.customMimeType;
+ }
+ queryFilterFields.push(`mimeType = '${mimeType}'`);
+ });
+
+ if (queryFilterFields.length) {
+ if (queryString !== '') {
+ queryString += ' and ';
+ }
+
+ queryString += queryFilterFields.join(' or ');
+ }
+ }
+ }
+
+ const pageSize = this.getNodeParameter('limit', i);
+
+ const qs = {
+ pageSize,
+ orderBy: 'modifiedTime',
+ fields: `nextPageToken, files(${queryFields})`,
+ spaces: querySpaces,
+ q: queryString,
+ includeItemsFromAllDrives: queryCorpora !== '' || driveId !== '',
+ supportsAllDrives: queryCorpora !== '' || driveId !== '',
+ };
+
+ const response = await googleApiRequest.call(this, 'GET', '/drive/v3/files', {}, qs);
+
+ const files = response.files;
+
+ const version = this.getNode().typeVersion;
+
+ const executionData = this.helpers.constructExecutionMetaData(
+ this.helpers.returnJsonArray(files as IDataObject[]),
+ { itemData: { item: i } },
+ );
+
+ if (version === 1) {
+ return [executionData];
+ }
+
+ returnData.push(...executionData);
+ } else if (operation === 'upload') {
+ // ----------------------------------
+ // upload
+ // ----------------------------------
+ const resolveData = this.getNodeParameter('resolveData', 0);
+
+ let contentLength: number;
+ let fileContent: Buffer | Readable;
+ let originalFilename: string | undefined;
+ let mimeType = 'text/plain';
+
+ if (this.getNodeParameter('binaryData', i)) {
+ const binaryPropertyName = this.getNodeParameter('binaryPropertyName', i);
+ const binaryData = this.helpers.assertBinaryData(i, binaryPropertyName);
+ if (binaryData.id) {
+ // Stream data in 256KB chunks, and upload the via the resumable upload api
+ fileContent = this.helpers.getBinaryStream(binaryData.id, UPLOAD_CHUNK_SIZE);
+ const metadata = await this.helpers.getBinaryMetadata(binaryData.id);
+ contentLength = metadata.fileSize;
+ originalFilename = metadata.fileName;
+ if (metadata.mimeType) mimeType = binaryData.mimeType;
+ } else {
+ fileContent = Buffer.from(binaryData.data, BINARY_ENCODING);
+ contentLength = fileContent.length;
+ originalFilename = binaryData.fileName;
+ mimeType = binaryData.mimeType;
+ }
+ } else {
+ // Is text file
+ fileContent = Buffer.from(this.getNodeParameter('fileContent', i) as string, 'utf8');
+ contentLength = fileContent.byteLength;
+ }
+
+ const name = this.getNodeParameter('name', i) as string;
+ const parents = this.getNodeParameter('parents', i) as string[];
+
+ let uploadId;
+ if (Buffer.isBuffer(fileContent)) {
+ const response = await googleApiRequest.call(
+ this,
+ 'POST',
+ '/upload/drive/v3/files',
+ fileContent,
+ {
+ fields: queryFields,
+ uploadType: 'media',
+ },
+ undefined,
+ {
+ headers: {
+ 'Content-Type': mimeType,
+ 'Content-Length': contentLength,
+ },
+ encoding: null,
+ json: false,
+ },
+ );
+ uploadId = JSON.parse(response as string).id;
+ } else {
+ const resumableUpload = await googleApiRequest.call(
+ this,
+ 'POST',
+ '/upload/drive/v3/files',
+ undefined,
+ { uploadType: 'resumable' },
+ undefined,
+ {
+ resolveWithFullResponse: true,
+ },
+ );
+ const uploadUrl = resumableUpload.headers.location;
+
+ let offset = 0;
+ for await (const chunk of fileContent) {
+ const nextOffset = offset + Number(chunk.length);
+ try {
+ const response = await this.helpers.httpRequest({
+ method: 'PUT',
+ url: uploadUrl,
+ headers: {
+ 'Content-Length': chunk.length,
+ 'Content-Range': `bytes ${offset}-${nextOffset - 1}/${contentLength}`,
+ },
+ body: chunk,
+ });
+ uploadId = response.id;
+ } catch (error) {
+ if (error.response?.status !== 308) throw error;
+ }
+ offset = nextOffset;
+ }
+ }
+
+ const requestBody = {
+ mimeType,
+ name,
+ originalFilename,
+ };
+
+ const properties = this.getNodeParameter(
+ 'options.propertiesUi.propertyValues',
+ i,
+ [],
+ ) as IDataObject[];
+
+ if (properties.length) {
+ Object.assign(requestBody, {
+ properties: properties.reduce(
+ (obj, value) => Object.assign(obj, { [`${value.key}`]: value.value }),
+ {},
+ ),
+ });
+ }
+
+ const appProperties = this.getNodeParameter(
+ 'options.appPropertiesUi.appPropertyValues',
+ i,
+ [],
+ ) as IDataObject[];
+
+ if (properties.length) {
+ Object.assign(requestBody, {
+ appProperties: appProperties.reduce(
+ (obj, value) => Object.assign(obj, { [`${value.key}`]: value.value }),
+ {},
+ ),
+ });
+ }
+
+ let response = await googleApiRequest.call(
+ this,
+ 'PATCH',
+ `/drive/v3/files/${uploadId}`,
+ requestBody,
+ {
+ addParents: parents.join(','),
+ // When set to true shared drives can be used.
+ supportsAllDrives: true,
+ },
+ );
+
+ if (resolveData) {
+ response = await googleApiRequest.call(
+ this,
+ 'GET',
+ `/drive/v3/files/${response.id}`,
+ {},
+ { fields: '*' },
+ );
+ }
+
+ const executionData = this.helpers.constructExecutionMetaData(
+ this.helpers.returnJsonArray(response as IDataObject[]),
+ { itemData: { item: i } },
+ );
+ returnData.push(...executionData);
+ } else if (operation === 'update') {
+ // ----------------------------------
+ // file:update
+ // ----------------------------------
+
+ const id = this.getNodeParameter('fileId', i, undefined, {
+ extractValue: true,
+ }) as string;
+ const updateFields = this.getNodeParameter('updateFields', i, {});
+
+ const qs: IDataObject = {
+ supportsAllDrives: true,
+ };
+
+ Object.assign(qs, options);
+
+ qs.fields = queryFields;
+
+ const body: IDataObject = {};
+
+ if (updateFields.fileName) {
+ body.name = updateFields.fileName;
+ }
+
+ if (updateFields.hasOwnProperty('trashed')) {
+ body.trashed = updateFields.trashed;
+ }
+
+ if (updateFields.parentId && updateFields.parentId !== '') {
+ qs.addParents = updateFields.parentId;
+ }
+
+ const responseData = await googleApiRequest.call(
+ this,
+ 'PATCH',
+ `/drive/v3/files/${id}`,
+ body,
+ qs,
+ );
+
+ const executionData = this.helpers.constructExecutionMetaData(
+ this.helpers.returnJsonArray(responseData as IDataObject[]),
+ { itemData: { item: i } },
+ );
+ returnData.push(...executionData);
+ }
+ }
+ if (resource === 'folder') {
+ if (operation === 'create') {
+ // ----------------------------------
+ // folder:create
+ // ----------------------------------
+
+ const name = this.getNodeParameter('name', i) as string;
+
+ const body = {
+ name,
+ mimeType: 'application/vnd.google-apps.folder',
+ parents: options.parents || [],
+ };
+
+ const qs = {
+ fields: queryFields,
+ supportsAllDrives: true,
+ };
+
+ const response = await googleApiRequest.call(this, 'POST', '/drive/v3/files', body, qs);
+
+ const executionData = this.helpers.constructExecutionMetaData(
+ this.helpers.returnJsonArray(response as IDataObject[]),
+ { itemData: { item: i } },
+ );
+ returnData.push(...executionData);
+ }
+ }
+ if (['file', 'folder'].includes(resource)) {
+ if (operation === 'delete') {
+ // ----------------------------------
+ // delete
+ // ----------------------------------
+
+ const fileId = this.getNodeParameter('fileId', i, undefined, {
+ extractValue: true,
+ }) as string;
+
+ await googleApiRequest.call(
+ this,
+ 'DELETE',
+ `/drive/v3/files/${fileId}`,
+ {},
+ { supportsTeamDrives: true },
+ );
+
+ // If we are still here it did succeed
+ const executionData = this.helpers.constructExecutionMetaData(
+ this.helpers.returnJsonArray({
+ fileId,
+ success: true,
+ }),
+ { itemData: { item: i } },
+ );
+
+ returnData.push(...executionData);
+ }
+ if (operation === 'share') {
+ const fileId = this.getNodeParameter('fileId', i, undefined, {
+ extractValue: true,
+ }) as string;
+
+ const permissions = this.getNodeParameter('permissionsUi', i) as IDataObject;
+
+ const shareOption = this.getNodeParameter('options', i);
+
+ const body: IDataObject = {};
+
+ const qs: IDataObject = {
+ supportsTeamDrives: true,
+ };
+
+ if (permissions.permissionsValues) {
+ Object.assign(body, permissions.permissionsValues);
+ }
+
+ Object.assign(qs, shareOption);
+
+ const response = await googleApiRequest.call(
+ this,
+ 'POST',
+ `/drive/v3/files/${fileId}/permissions`,
+ body,
+ qs,
+ );
+
+ const executionData = this.helpers.constructExecutionMetaData(
+ this.helpers.returnJsonArray(response as IDataObject[]),
+ { itemData: { item: i } },
+ );
+ returnData.push(...executionData);
+ }
+ }
+ } catch (error) {
+ if (this.continueOnFail()) {
+ if (resource === 'file' && operation === 'download') {
+ items[i].json = { error: error.message };
+ } else {
+ returnData.push({ json: { error: error.message } });
+ }
+ continue;
+ }
+ throw error;
+ }
+ }
+ if (resource === 'file' && operation === 'download') {
+ // For file downloads the files get attached to the existing items
+ return this.prepareOutputData(items);
+ } else {
+ // For all other ones does the output items get replaced
+ return this.prepareOutputData(returnData);
+ }
+ }
+}
diff --git a/packages/nodes-base/nodes/Google/Drive/SearchFunctions.ts b/packages/nodes-base/nodes/Google/Drive/v1/SearchFunctions.ts
similarity index 100%
rename from packages/nodes-base/nodes/Google/Drive/SearchFunctions.ts
rename to packages/nodes-base/nodes/Google/Drive/v1/SearchFunctions.ts
diff --git a/packages/nodes-base/nodes/Google/Drive/v2/GoogleDriveV2.node.ts b/packages/nodes-base/nodes/Google/Drive/v2/GoogleDriveV2.node.ts
new file mode 100644
index 0000000000..7a807dafe2
--- /dev/null
+++ b/packages/nodes-base/nodes/Google/Drive/v2/GoogleDriveV2.node.ts
@@ -0,0 +1,28 @@
+/* eslint-disable n8n-nodes-base/node-filename-against-convention */
+import type {
+ IExecuteFunctions,
+ INodeType,
+ INodeTypeBaseDescription,
+ INodeTypeDescription,
+} from 'n8n-workflow';
+
+import { versionDescription } from './actions/versionDescription';
+import { listSearch } from './methods';
+import { router } from './actions/router';
+
+export class GoogleDriveV2 implements INodeType {
+ description: INodeTypeDescription;
+
+ constructor(baseDescription: INodeTypeBaseDescription) {
+ this.description = {
+ ...baseDescription,
+ ...versionDescription,
+ };
+ }
+
+ methods = { listSearch };
+
+ async execute(this: IExecuteFunctions) {
+ return router.call(this);
+ }
+}
diff --git a/packages/nodes-base/nodes/Google/Drive/v2/actions/common.descriptions.ts b/packages/nodes-base/nodes/Google/Drive/v2/actions/common.descriptions.ts
new file mode 100644
index 0000000000..d9179ff185
--- /dev/null
+++ b/packages/nodes-base/nodes/Google/Drive/v2/actions/common.descriptions.ts
@@ -0,0 +1,623 @@
+import type { INodeProperties } from 'n8n-workflow';
+import { DRIVE, RLC_DRIVE_DEFAULT } from '../helpers/interfaces';
+
+export const fileRLC: INodeProperties = {
+ displayName: 'File',
+ name: 'fileId',
+ type: 'resourceLocator',
+ default: { mode: 'list', value: '' },
+ required: true,
+ modes: [
+ {
+ displayName: 'File',
+ name: 'list',
+ type: 'list',
+ placeholder: 'Select a file...',
+ typeOptions: {
+ searchListMethod: 'fileSearch',
+ searchable: true,
+ },
+ },
+ {
+ displayName: 'Link',
+ name: 'url',
+ type: 'string',
+ placeholder:
+ 'e.g. https://drive.google.com/file/d/1anGBg0b5re2VtF2bKu201_a-Vnz5BHq9Y4r-yBDAj5A/edit',
+ extractValue: {
+ type: 'regex',
+ regex:
+ 'https:\\/\\/(?:drive|docs)\\.google\\.com\\/\\w+\\/d\\/([0-9a-zA-Z\\-_]+)(?:\\/.*|)',
+ },
+ validation: [
+ {
+ type: 'regex',
+ properties: {
+ regex:
+ 'https:\\/\\/(?:drive|docs)\\.google.com\\/\\w+\\/d\\/([0-9a-zA-Z\\-_]+)(?:\\/.*|)',
+ errorMessage: 'Not a valid Google Drive File URL',
+ },
+ },
+ ],
+ },
+ {
+ displayName: 'ID',
+ name: 'id',
+ type: 'string',
+ placeholder: 'e.g. 1anGBg0b5re2VtF2bKu201_a-Vnz5BHq9Y4r-yBDAj5A',
+ validation: [
+ {
+ type: 'regex',
+ properties: {
+ regex: '[a-zA-Z0-9\\-_]{2,}',
+ errorMessage: 'Not a valid Google Drive File ID',
+ },
+ },
+ ],
+ url: '=https://drive.google.com/file/d/{{$value}}/view',
+ },
+ ],
+ description: 'The file to operate on',
+};
+
+export const folderNoRootRLC: INodeProperties = {
+ displayName: 'Folder',
+ name: 'folderNoRootId',
+ type: 'resourceLocator',
+ default: { mode: 'list', value: '' },
+ required: true,
+ modes: [
+ {
+ displayName: 'Folder',
+ name: 'list',
+ type: 'list',
+ placeholder: 'Select a folder...',
+ typeOptions: {
+ searchListMethod: 'folderSearch',
+ searchable: true,
+ },
+ },
+ {
+ displayName: 'Link',
+ name: 'url',
+ type: 'string',
+ placeholder: 'e.g. https://drive.google.com/drive/folders/1Tx9WHbA3wBpPB4C_HcoZDH9WZFWYxAMU',
+ extractValue: {
+ type: 'regex',
+ regex:
+ 'https:\\/\\/drive\\.google\\.com(?:\\/.*|)\\/folders\\/([0-9a-zA-Z\\-_]+)(?:\\/.*|)',
+ },
+ validation: [
+ {
+ type: 'regex',
+ properties: {
+ regex:
+ 'https:\\/\\/drive\\.google\\.com(?:\\/.*|)\\/folders\\/([0-9a-zA-Z\\-_]+)(?:\\/.*|)',
+ errorMessage: 'Not a valid Google Drive Folder URL',
+ },
+ },
+ ],
+ },
+ {
+ displayName: 'ID',
+ name: 'id',
+ type: 'string',
+ placeholder: 'e.g. 1anGBg0b5re2VtF2bKu201_a-Vnz5BHq9Y4r-yBDAj5A',
+ validation: [
+ {
+ type: 'regex',
+ properties: {
+ regex: '[a-zA-Z0-9\\-_]{2,}',
+ errorMessage: 'Not a valid Google Drive Folder ID',
+ },
+ },
+ ],
+ url: '=https://drive.google.com/drive/folders/{{$value}}',
+ },
+ ],
+ description: 'The folder to operate on',
+};
+
+export const folderRLC: INodeProperties = {
+ displayName: 'Folder',
+ name: 'folderId',
+ type: 'resourceLocator',
+ default: { mode: 'list', value: 'root', cachedResultName: '/ (Root folder)' },
+ required: true,
+ modes: [
+ {
+ displayName: 'Folder',
+ name: 'list',
+ type: 'list',
+ placeholder: 'Select a folder...',
+ typeOptions: {
+ searchListMethod: 'folderSearchWithDefault',
+ searchable: true,
+ },
+ },
+ {
+ displayName: 'Link',
+ name: 'url',
+ type: 'string',
+ placeholder: 'e.g. https://drive.google.com/drive/folders/1Tx9WHbA3wBpPB4C_HcoZDH9WZFWYxAMU',
+ extractValue: {
+ type: 'regex',
+ regex:
+ 'https:\\/\\/drive\\.google\\.com(?:\\/.*|)\\/folders\\/([0-9a-zA-Z\\-_]+)(?:\\/.*|)',
+ },
+ validation: [
+ {
+ type: 'regex',
+ properties: {
+ regex:
+ 'https:\\/\\/drive\\.google\\.com(?:\\/.*|)\\/folders\\/([0-9a-zA-Z\\-_]+)(?:\\/.*|)',
+ errorMessage: 'Not a valid Google Drive Folder URL',
+ },
+ },
+ ],
+ },
+ {
+ displayName: 'ID',
+ name: 'id',
+ type: 'string',
+ placeholder: 'e.g. 1anGBg0b5re2VtF2bKu201_a-Vnz5BHq9Y4r-yBDAj5A',
+ validation: [
+ {
+ type: 'regex',
+ properties: {
+ regex: '[a-zA-Z0-9\\-_]{2,}',
+ errorMessage: 'Not a valid Google Drive Folder ID',
+ },
+ },
+ ],
+ url: '=https://drive.google.com/drive/folders/{{$value}}',
+ },
+ ],
+ description: 'The folder to operate on',
+};
+
+export const driveRLC: INodeProperties = {
+ displayName: 'Drive',
+ name: 'driveId',
+ type: 'resourceLocator',
+ default: { mode: 'list', value: RLC_DRIVE_DEFAULT },
+ required: true,
+ modes: [
+ {
+ displayName: 'Drive',
+ name: 'list',
+ type: 'list',
+ placeholder: 'Select a drive...',
+ typeOptions: {
+ searchListMethod: 'driveSearchWithDefault',
+ searchable: true,
+ },
+ },
+ {
+ displayName: 'Link',
+ name: 'url',
+ type: 'string',
+ placeholder: 'https://drive.google.com/drive/folders/0AaaaaAAAAAAAaa',
+ extractValue: {
+ type: 'regex',
+ regex:
+ 'https:\\/\\/drive\\.google\\.com(?:\\/.*|)\\/folders\\/([0-9a-zA-Z\\-_]+)(?:\\/.*|)',
+ },
+ validation: [
+ {
+ type: 'regex',
+ properties: {
+ regex:
+ 'https:\\/\\/drive\\.google\\.com(?:\\/.*|)\\/folders\\/([0-9a-zA-Z\\-_]+)(?:\\/.*|)',
+ errorMessage: 'Not a valid Google Drive Drive URL',
+ },
+ },
+ ],
+ },
+ {
+ displayName: 'ID',
+ name: 'id',
+ type: 'string',
+ hint: 'The ID of the shared drive',
+ validation: [
+ {
+ type: 'regex',
+ properties: {
+ regex: '[a-zA-Z0-9\\-_]{2,}',
+ errorMessage: 'Not a valid Google Drive Drive ID',
+ },
+ },
+ ],
+ url: '=https://drive.google.com/drive/folders/{{$value}}',
+ },
+ ],
+ description: 'The ID of the drive',
+};
+
+export const sharedDriveRLC: INodeProperties = {
+ displayName: 'Shared Drive',
+ name: 'driveId',
+ type: 'resourceLocator',
+ default: { mode: 'list', value: '' },
+ required: true,
+ modes: [
+ {
+ displayName: 'Drive',
+ name: 'list',
+ type: 'list',
+ placeholder: 'Select a shared drive...',
+ typeOptions: {
+ searchListMethod: 'driveSearch',
+ searchable: true,
+ },
+ },
+ {
+ displayName: 'Link',
+ name: 'url',
+ type: 'string',
+ placeholder: 'e.g. https://drive.google.com/drive/u/1/folders/0AIjtcbwnjtcbwn9PVA',
+ extractValue: {
+ type: 'regex',
+ regex:
+ 'https:\\/\\/drive\\.google\\.com(?:\\/.*|)\\/folders\\/([0-9a-zA-Z\\-_]+)(?:\\/.*|)',
+ },
+ validation: [
+ {
+ type: 'regex',
+ properties: {
+ regex:
+ 'https:\\/\\/drive\\.google\\.com(?:\\/.*|)\\/folders\\/([0-9a-zA-Z\\-_]+)(?:\\/.*|)',
+ errorMessage: 'Not a valid Google Drive Drive URL',
+ },
+ },
+ ],
+ },
+ {
+ displayName: 'ID',
+ name: 'id',
+ type: 'string',
+ // hint: 'The ID of the shared drive',
+ placeholder: 'e.g. 0AMXTKI5ZSiM7Uk9PVA',
+ validation: [
+ {
+ type: 'regex',
+ properties: {
+ regex: '[a-zA-Z0-9\\-_]{2,}',
+ errorMessage: 'Not a valid Google Drive Drive ID',
+ },
+ },
+ ],
+ url: '=https://drive.google.com/drive/folders/{{$value}}',
+ },
+ ],
+ description: 'The shared drive to operate on',
+};
+
+export const shareOptions: INodeProperties = {
+ displayName: 'Options',
+ name: 'options',
+ type: 'collection',
+ placeholder: 'Add Option',
+ default: {},
+ options: [
+ {
+ displayName: 'Email Message',
+ name: 'emailMessage',
+ type: 'string',
+ default: '',
+ description: 'A plain text custom message to include in the notification email',
+ typeOptions: {
+ rows: 2,
+ },
+ },
+ {
+ displayName: 'Move To New Owners Root',
+ name: 'moveToNewOwnersRoot',
+ type: 'boolean',
+ default: false,
+ // eslint-disable-next-line n8n-nodes-base/node-param-description-boolean-without-whether
+ description:
+ "This parameter only takes effect if the item is not in a shared drive and the request is attempting to transfer the ownership of the item.
When set to true, the item is moved to the new owner's My Drive root folder and all prior parents removed.
",
+ },
+ {
+ displayName: 'Send Notification Email',
+ name: 'sendNotificationEmail',
+ type: 'boolean',
+ default: false,
+ description: 'Whether to send a notification email when sharing to users or groups',
+ },
+ {
+ displayName: 'Transfer Ownership',
+ name: 'transferOwnership',
+ type: 'boolean',
+ default: false,
+ description:
+ 'Whether to transfer ownership to the specified user and downgrade the current owner to a writer',
+ },
+ {
+ displayName: 'Use Domain Admin Access',
+ name: 'useDomainAdminAccess',
+ type: 'boolean',
+ default: false,
+ description:
+ 'Whether to perform the operation as domain administrator, i.e. if you are an administrator of the domain to which the shared drive belongs, you will be granted access automatically.',
+ },
+ ],
+};
+
+export const permissionsOptions: INodeProperties = {
+ displayName: 'Permissions',
+ name: 'permissionsUi',
+ placeholder: 'Add Permission',
+ type: 'fixedCollection',
+ default: {},
+ typeOptions: {
+ multipleValues: false,
+ },
+ options: [
+ {
+ displayName: 'Permission',
+ name: 'permissionsValues',
+ values: [
+ {
+ displayName: 'Role',
+ name: 'role',
+ type: 'options',
+ description: 'Defines what users can do with the file or folder',
+ options: [
+ {
+ name: 'Commenter',
+ value: 'commenter',
+ },
+ {
+ name: 'File Organizer',
+ value: 'fileOrganizer',
+ },
+ {
+ name: 'Organizer',
+ value: 'organizer',
+ },
+ {
+ name: 'Owner',
+ value: 'owner',
+ },
+ {
+ name: 'Reader',
+ value: 'reader',
+ },
+ {
+ name: 'Writer',
+ value: 'writer',
+ },
+ ],
+ default: '',
+ },
+ {
+ displayName: 'Type',
+ name: 'type',
+ type: 'options',
+ options: [
+ {
+ name: 'User',
+ value: 'user',
+ },
+ {
+ name: 'Group',
+ value: 'group',
+ },
+ {
+ name: 'Domain',
+ value: 'domain',
+ },
+ {
+ name: 'Anyone',
+ value: 'anyone',
+ },
+ ],
+ default: '',
+ description:
+ 'The scope of the permission. A permission with type=user applies to a specific user whereas a permission with type=domain applies to everyone in a specific domain.',
+ },
+ {
+ displayName: 'Email Address',
+ name: 'emailAddress',
+ type: 'string',
+ displayOptions: {
+ show: {
+ type: ['user', 'group'],
+ },
+ },
+ placeholder: '“e.g. name@mail.com',
+ default: '',
+ description: 'The email address of the user or group to which this permission refers',
+ },
+ {
+ displayName: 'Domain',
+ name: 'domain',
+ type: 'string',
+ displayOptions: {
+ show: {
+ type: ['domain'],
+ },
+ },
+ placeholder: 'e.g. mycompany.com',
+ default: '',
+ description: 'The domain to which this permission refers',
+ },
+ {
+ displayName: 'Allow File Discovery',
+ name: 'allowFileDiscovery',
+ type: 'boolean',
+ displayOptions: {
+ show: {
+ type: ['domain', 'anyone'],
+ },
+ },
+ default: false,
+ description: 'Whether to allow the file to be discovered through search',
+ },
+ ],
+ },
+ ],
+};
+
+export const updateCommonOptions: INodeProperties[] = [
+ {
+ displayName: 'APP Properties',
+ name: 'appPropertiesUi',
+ placeholder: 'Add Property',
+ type: 'fixedCollection',
+ default: {},
+ typeOptions: {
+ multipleValues: true,
+ },
+ description:
+ 'A collection of arbitrary key-value pairs which are private to the requesting app',
+ options: [
+ {
+ name: 'appPropertyValues',
+ displayName: 'APP Property',
+ values: [
+ {
+ displayName: 'Key',
+ name: 'key',
+ type: 'string',
+ default: '',
+ description: 'Name of the key to add',
+ },
+ {
+ displayName: 'Value',
+ name: 'value',
+ type: 'string',
+ default: '',
+ description: 'Value to set for the key',
+ },
+ ],
+ },
+ ],
+ },
+ {
+ displayName: 'Properties',
+ name: 'propertiesUi',
+ placeholder: 'Add Property',
+ type: 'fixedCollection',
+ default: {},
+ typeOptions: {
+ multipleValues: true,
+ },
+ description: 'A collection of arbitrary key-value pairs which are visible to all apps',
+ options: [
+ {
+ name: 'propertyValues',
+ displayName: 'Property',
+ values: [
+ {
+ displayName: 'Key',
+ name: 'key',
+ type: 'string',
+ default: '',
+ description: 'Name of the key to add',
+ },
+ {
+ displayName: 'Value',
+ name: 'value',
+ type: 'string',
+ default: '',
+ description: 'Value to set for the key',
+ },
+ ],
+ },
+ ],
+ },
+ {
+ displayName: 'Keep Revision Forever',
+ name: 'keepRevisionForever',
+ type: 'boolean',
+ default: false,
+ description:
+ "Whether to set the 'keepForever' field in the new head revision. This is only applicable to files with binary content in Google Drive. Only 200 revisions for the file can be kept forever. If the limit is reached, try deleting pinned revisions.",
+ },
+ {
+ displayName: 'OCR Language',
+ name: 'ocrLanguage',
+ type: 'string',
+ default: '',
+ placeholder: 'e.g. en',
+ description: 'A language hint for OCR processing during image import (ISO 639-1 code)',
+ },
+ {
+ displayName: 'Use Content As Indexable Text',
+ name: 'useContentAsIndexableText',
+ type: 'boolean',
+ default: false,
+ description: 'Whether to use the uploaded content as indexable text',
+ },
+];
+
+export const fileTypesOptions = [
+ {
+ name: 'All',
+ value: '*',
+ description: 'Return all file types',
+ },
+ {
+ name: '3rd Party Shortcut',
+ value: DRIVE.SDK,
+ },
+ {
+ name: 'Audio',
+ value: DRIVE.AUDIO,
+ },
+ {
+ name: 'Folder',
+ value: DRIVE.FOLDER,
+ },
+ {
+ name: 'Google Apps Scripts',
+ value: DRIVE.APP_SCRIPTS,
+ },
+ {
+ name: 'Google Docs',
+ value: DRIVE.DOCUMENT,
+ },
+ {
+ name: 'Google Drawing',
+ value: DRIVE.DRAWING,
+ },
+ {
+ name: 'Google Forms',
+ value: DRIVE.FORM,
+ },
+ {
+ name: 'Google Fusion Tables',
+ value: DRIVE.FUSIONTABLE,
+ },
+ {
+ name: 'Google My Maps',
+ value: DRIVE.MAP,
+ },
+ {
+ name: 'Google Sheets',
+ value: DRIVE.SPREADSHEET,
+ },
+ {
+ name: 'Google Sites',
+ value: DRIVE.SITES,
+ },
+ {
+ name: 'Google Slides',
+ value: DRIVE.PRESENTATION,
+ },
+ {
+ name: 'Photo',
+ value: DRIVE.PHOTO,
+ },
+ {
+ name: 'Unknown',
+ value: DRIVE.UNKNOWN,
+ },
+ {
+ name: 'Video',
+ value: DRIVE.VIDEO,
+ },
+];
diff --git a/packages/nodes-base/nodes/Google/Drive/v2/actions/drive/Drive.resource.ts b/packages/nodes-base/nodes/Google/Drive/v2/actions/drive/Drive.resource.ts
new file mode 100644
index 0000000000..19d82cb1eb
--- /dev/null
+++ b/packages/nodes-base/nodes/Google/Drive/v2/actions/drive/Drive.resource.ts
@@ -0,0 +1,61 @@
+import type { INodeProperties } from 'n8n-workflow';
+
+import * as create from './create.operation';
+import * as deleteDrive from './deleteDrive.operation';
+import * as get from './get.operation';
+import * as list from './list.operation';
+import * as update from './update.operation';
+
+export { create, deleteDrive, get, list, update };
+
+export const description: INodeProperties[] = [
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ noDataExpression: true,
+ options: [
+ {
+ name: 'Create',
+ value: 'create',
+ description: 'Create a shared drive',
+ action: 'Create shared drive',
+ },
+ {
+ name: 'Delete',
+ value: 'deleteDrive',
+ description: 'Permanently delete a shared drive',
+ action: 'Delete shared drive',
+ },
+ {
+ name: 'Get',
+ value: 'get',
+ description: 'Get a shared drive',
+ action: 'Get shared drive',
+ },
+ {
+ name: 'Get Many',
+ value: 'list',
+ description: 'Get the list of shared drives',
+ action: 'Get many shared drives',
+ },
+ {
+ name: 'Update',
+ value: 'update',
+ description: 'Update a shared drive',
+ action: 'Update shared drive',
+ },
+ ],
+ default: 'create',
+ displayOptions: {
+ show: {
+ resource: ['drive'],
+ },
+ },
+ },
+ ...create.description,
+ ...deleteDrive.description,
+ ...get.description,
+ ...list.description,
+ ...update.description,
+];
diff --git a/packages/nodes-base/nodes/Google/Drive/v2/actions/drive/create.operation.ts b/packages/nodes-base/nodes/Google/Drive/v2/actions/drive/create.operation.ts
new file mode 100644
index 0000000000..47f3be8a0b
--- /dev/null
+++ b/packages/nodes-base/nodes/Google/Drive/v2/actions/drive/create.operation.ts
@@ -0,0 +1,263 @@
+import type { IExecuteFunctions } from 'n8n-core';
+import type { IDataObject, INodeExecutionData, INodeProperties } from 'n8n-workflow';
+
+import { updateDisplayOptions } from '../../../../../../utils/utilities';
+import { googleApiRequest } from '../../transport';
+
+import { v4 as uuid } from 'uuid';
+
+const properties: INodeProperties[] = [
+ {
+ displayName: 'Name',
+ name: 'name',
+ type: 'string',
+ default: '',
+ placeholder: 'e.g. New Shared Drive',
+ description: 'The name of the shared drive to create',
+ },
+ {
+ displayName: 'Options',
+ name: 'options',
+ type: 'collection',
+ placeholder: 'Add Option',
+ default: {},
+ options: [
+ {
+ displayName: 'Capabilities',
+ name: 'capabilities',
+ type: 'collection',
+ placeholder: 'Add Field',
+ default: {},
+ options: [
+ {
+ displayName: 'Can Add Children',
+ name: 'canAddChildren',
+ type: 'boolean',
+ default: false,
+ description:
+ 'Whether the current user can add children to folders in this shared drive',
+ },
+ {
+ displayName: 'Can Change Copy Requires Writer Permission Restriction',
+ name: 'canChangeCopyRequiresWriterPermissionRestriction',
+ type: 'boolean',
+ default: false,
+ description:
+ 'Whether the current user can change the copyRequiresWriterPermission restriction of this shared drive',
+ },
+ {
+ displayName: 'Can Change Domain Users Only Restriction',
+ name: 'canChangeDomainUsersOnlyRestriction',
+ type: 'boolean',
+ default: false,
+ description:
+ 'Whether the current user can change the domainUsersOnly restriction of this shared drive',
+ },
+ {
+ displayName: 'Can Change Drive Background',
+ name: 'canChangeDriveBackground',
+ type: 'boolean',
+ default: false,
+ description: 'Whether the current user can change the background of this shared drive',
+ },
+ {
+ displayName: 'Can Change Drive Members Only Restriction',
+ name: 'canChangeDriveMembersOnlyRestriction',
+ type: 'boolean',
+ default: false,
+ description:
+ 'Whether the current user can change the driveMembersOnly restriction of this shared drive',
+ },
+ {
+ displayName: 'Can Comment',
+ name: 'canComment',
+ type: 'boolean',
+ default: false,
+ description: 'Whether the current user can comment on files in this shared drive',
+ },
+ {
+ displayName: 'Can Copy',
+ name: 'canCopy',
+ type: 'boolean',
+ default: false,
+ description: 'Whether the current user can copy files in this shared drive',
+ },
+ {
+ displayName: 'Can Delete Children',
+ name: 'canDeleteChildren',
+ type: 'boolean',
+ default: false,
+ description:
+ 'Whether the current user can delete children from folders in this shared drive',
+ },
+ {
+ displayName: 'Can Delete Drive',
+ name: 'canDeleteDrive',
+ type: 'boolean',
+ default: false,
+ description:
+ 'Whether the current user can delete this shared drive. Attempting to delete the shared drive may still fail if there are untrashed items inside the shared drive.',
+ },
+ {
+ displayName: 'Can Download',
+ name: 'canDownload',
+ type: 'boolean',
+ default: false,
+ description: 'Whether the current user can download files in this shared drive',
+ },
+ {
+ displayName: 'Can Edit',
+ name: 'canEdit',
+ type: 'boolean',
+ default: false,
+ description: 'Whether the current user can edit files in this shared drive',
+ },
+ {
+ displayName: 'Can List Children',
+ name: 'canListChildren',
+ type: 'boolean',
+ default: false,
+ description:
+ 'Whether the current user can list the children of folders in this shared drive',
+ },
+ {
+ displayName: 'Can Manage Members',
+ name: 'canManageMembers',
+ type: 'boolean',
+ default: false,
+ description:
+ 'Whether the current user can add members to this shared drive or remove them or change their role',
+ },
+ {
+ displayName: 'Can Read Revisions',
+ name: 'canReadRevisions',
+ type: 'boolean',
+ default: false,
+ description:
+ 'Whether the current user can read the revisions resource of files in this shared drive',
+ },
+ {
+ displayName: 'Can Rename',
+ name: 'canRename',
+ type: 'boolean',
+ default: false,
+ description:
+ 'Whether the current user can rename files or folders in this shared drive',
+ },
+ {
+ displayName: 'Can Rename Drive',
+ name: 'canRenameDrive',
+ type: 'boolean',
+ default: false,
+ description: 'Whether the current user can rename this shared drive',
+ },
+ {
+ displayName: 'Can Share',
+ name: 'canShare',
+ type: 'boolean',
+ default: false,
+ description: 'Whether the current user can rename this shared drive',
+ },
+ {
+ displayName: 'Can Trash Children',
+ name: 'canTrashChildren',
+ type: 'boolean',
+ default: false,
+ description:
+ 'Whether the current user can trash children from folders in this shared drive',
+ },
+ ],
+ },
+ {
+ displayName: 'Color RGB',
+ name: 'colorRgb',
+ type: 'color',
+ default: '',
+ description: 'The color of this shared drive as an RGB hex string',
+ },
+ {
+ displayName: 'Hidden',
+ name: 'hidden',
+ type: 'boolean',
+ default: false,
+ description: 'Whether the shared drive is hidden from default view',
+ },
+ {
+ displayName: 'Restrictions',
+ name: 'restrictions',
+ type: 'collection',
+ placeholder: 'Add Field',
+ default: {},
+ options: [
+ {
+ displayName: 'Admin Managed Restrictions',
+ name: 'adminManagedRestrictions',
+ type: 'boolean',
+ default: false,
+ description:
+ 'Whether the options to copy, print, or download files inside this shared drive, should be disabled for readers and commenters. When this restriction is set to true, it will override the similarly named field to true for any file inside this shared drive.',
+ },
+ {
+ displayName: 'Copy Requires Writer Permission',
+ name: 'copyRequiresWriterPermission',
+ type: 'boolean',
+ default: false,
+ description:
+ 'Whether the options to copy, print, or download files inside this shared drive, should be disabled for readers and commenters. When this restriction is set to true, it will override the similarly named field to true for any file inside this shared drive.',
+ },
+ {
+ displayName: 'Domain Users Only',
+ name: 'domainUsersOnly',
+ type: 'boolean',
+ default: false,
+ description:
+ 'Whether access to this shared drive and items inside this shared drive is restricted to users of the domain to which this shared drive belongs. This restriction may be overridden by other sharing policies controlled outside of this shared drive.',
+ },
+ {
+ displayName: 'Drive Members Only',
+ name: 'driveMembersOnly',
+ type: 'boolean',
+ default: false,
+ description:
+ 'Whether access to items inside this shared drive is restricted to its members',
+ },
+ ],
+ },
+ ],
+ },
+];
+
+const displayOptions = {
+ show: {
+ resource: ['drive'],
+ operation: ['create'],
+ },
+};
+
+export const description = updateDisplayOptions(displayOptions, properties);
+
+export async function execute(this: IExecuteFunctions, i: number): Promise {
+ const returnData: INodeExecutionData[] = [];
+ const options = this.getNodeParameter('options', i);
+
+ const name = this.getNodeParameter('name', i) as string;
+
+ const body: IDataObject = {
+ name,
+ };
+
+ Object.assign(body, options);
+
+ const response = await googleApiRequest.call(this, 'POST', '/drive/v3/drives', body, {
+ requestId: uuid(),
+ });
+
+ const executionData = this.helpers.constructExecutionMetaData(
+ this.helpers.returnJsonArray(response as IDataObject[]),
+ { itemData: { item: i } },
+ );
+
+ returnData.push(...executionData);
+
+ return returnData;
+}
diff --git a/packages/nodes-base/nodes/Google/Drive/v2/actions/drive/deleteDrive.operation.ts b/packages/nodes-base/nodes/Google/Drive/v2/actions/drive/deleteDrive.operation.ts
new file mode 100644
index 0000000000..080c103eab
--- /dev/null
+++ b/packages/nodes-base/nodes/Google/Drive/v2/actions/drive/deleteDrive.operation.ts
@@ -0,0 +1,41 @@
+import type { IExecuteFunctions } from 'n8n-core';
+import type { INodeExecutionData, INodeProperties } from 'n8n-workflow';
+
+import { updateDisplayOptions } from '../../../../../../utils/utilities';
+import { googleApiRequest } from '../../transport';
+import { sharedDriveRLC } from '../common.descriptions';
+
+const properties: INodeProperties[] = [
+ {
+ ...sharedDriveRLC,
+ description: 'The shared drive to delete',
+ },
+];
+
+const displayOptions = {
+ show: {
+ resource: ['drive'],
+ operation: ['deleteDrive'],
+ },
+};
+
+export const description = updateDisplayOptions(displayOptions, properties);
+
+export async function execute(this: IExecuteFunctions, i: number): Promise {
+ const returnData: INodeExecutionData[] = [];
+
+ const driveId = this.getNodeParameter('driveId', i, undefined, {
+ extractValue: true,
+ }) as string;
+
+ await googleApiRequest.call(this, 'DELETE', `/drive/v3/drives/${driveId}`);
+
+ const executionData = this.helpers.constructExecutionMetaData(
+ this.helpers.returnJsonArray({ success: true }),
+ { itemData: { item: i } },
+ );
+
+ returnData.push(...executionData);
+
+ return returnData;
+}
diff --git a/packages/nodes-base/nodes/Google/Drive/v2/actions/drive/get.operation.ts b/packages/nodes-base/nodes/Google/Drive/v2/actions/drive/get.operation.ts
new file mode 100644
index 0000000000..cab2ee8a48
--- /dev/null
+++ b/packages/nodes-base/nodes/Google/Drive/v2/actions/drive/get.operation.ts
@@ -0,0 +1,63 @@
+import type { IExecuteFunctions } from 'n8n-core';
+import type { IDataObject, INodeExecutionData, INodeProperties } from 'n8n-workflow';
+
+import { updateDisplayOptions } from '../../../../../../utils/utilities';
+import { googleApiRequest } from '../../transport';
+import { sharedDriveRLC } from '../common.descriptions';
+
+const properties: INodeProperties[] = [
+ {
+ ...sharedDriveRLC,
+ description: 'The shared drive to get',
+ },
+ {
+ displayName: 'Options',
+ name: 'options',
+ type: 'collection',
+ placeholder: 'Add Option',
+ default: {},
+ options: [
+ {
+ displayName: 'Use Domain Admin Access',
+ name: 'useDomainAdminAccess',
+ type: 'boolean',
+ default: false,
+ description:
+ 'Whether to issue the request as a domain administrator; if set to true, then the requester will be granted access if they are an administrator of the domain to which the shared drive belongs',
+ },
+ ],
+ },
+];
+
+const displayOptions = {
+ show: {
+ resource: ['drive'],
+ operation: ['get'],
+ },
+};
+
+export const description = updateDisplayOptions(displayOptions, properties);
+
+export async function execute(this: IExecuteFunctions, i: number): Promise {
+ const returnData: INodeExecutionData[] = [];
+ const options = this.getNodeParameter('options', i);
+
+ const driveId = this.getNodeParameter('driveId', i, undefined, {
+ extractValue: true,
+ }) as string;
+
+ const qs: IDataObject = {};
+
+ Object.assign(qs, options);
+
+ const response = await googleApiRequest.call(this, 'GET', `/drive/v3/drives/${driveId}`, {}, qs);
+
+ const executionData = this.helpers.constructExecutionMetaData(
+ this.helpers.returnJsonArray(response as IDataObject[]),
+ { itemData: { item: i } },
+ );
+
+ returnData.push(...executionData);
+
+ return returnData;
+}
diff --git a/packages/nodes-base/nodes/Google/Drive/v2/actions/drive/list.operation.ts b/packages/nodes-base/nodes/Google/Drive/v2/actions/drive/list.operation.ts
new file mode 100644
index 0000000000..cd0fffa2ca
--- /dev/null
+++ b/packages/nodes-base/nodes/Google/Drive/v2/actions/drive/list.operation.ts
@@ -0,0 +1,103 @@
+import type { IExecuteFunctions } from 'n8n-core';
+import type { IDataObject, INodeExecutionData, INodeProperties } from 'n8n-workflow';
+
+import { updateDisplayOptions } from '../../../../../../utils/utilities';
+
+import { googleApiRequest, googleApiRequestAllItems } from '../../transport';
+
+const properties: INodeProperties[] = [
+ {
+ displayName: 'Return All',
+ name: 'returnAll',
+ type: 'boolean',
+ default: false,
+ description: 'Whether to return all results or only up to a given limit',
+ },
+ {
+ displayName: 'Limit',
+ name: 'limit',
+ type: 'number',
+ displayOptions: {
+ show: {
+ returnAll: [false],
+ },
+ },
+ typeOptions: {
+ minValue: 1,
+ maxValue: 200,
+ },
+ default: 100,
+ description: 'Max number of results to return',
+ },
+ {
+ displayName: 'Options',
+ name: 'options',
+ type: 'collection',
+ placeholder: 'Add Option',
+ default: {},
+ options: [
+ {
+ displayName: 'Query',
+ name: 'q',
+ type: 'string',
+ default: '',
+ description:
+ 'Query string for searching shared drives. See the "Search for shared drives" guide for supported syntax.',
+ },
+ {
+ displayName: 'Use Domain Admin Access',
+ name: 'useDomainAdminAccess',
+ type: 'boolean',
+ default: false,
+ description:
+ 'Whether to issue the request as a domain administrator; if set to true, then the requester will be granted access if they are an administrator of the domain to which the shared drive belongs',
+ },
+ ],
+ },
+];
+
+const displayOptions = {
+ show: {
+ resource: ['drive'],
+ operation: ['list'],
+ },
+};
+
+export const description = updateDisplayOptions(displayOptions, properties);
+
+export async function execute(this: IExecuteFunctions, i: number): Promise {
+ const returnData: INodeExecutionData[] = [];
+ const options = this.getNodeParameter('options', i);
+
+ const returnAll = this.getNodeParameter('returnAll', i);
+
+ const qs: IDataObject = {};
+
+ let response: IDataObject[] = [];
+
+ Object.assign(qs, options);
+
+ if (returnAll) {
+ response = await googleApiRequestAllItems.call(
+ this,
+ 'GET',
+ 'drives',
+ '/drive/v3/drives',
+ {},
+ qs,
+ );
+ } else {
+ qs.pageSize = this.getNodeParameter('limit', i);
+ const data = await googleApiRequest.call(this, 'GET', '/drive/v3/drives', {}, qs);
+ response = data.drives as IDataObject[];
+ }
+
+ const executionData = this.helpers.constructExecutionMetaData(
+ this.helpers.returnJsonArray(response),
+ { itemData: { item: i } },
+ );
+
+ returnData.push(...executionData);
+
+ return returnData;
+}
diff --git a/packages/nodes-base/nodes/Google/Drive/v2/actions/drive/update.operation.ts b/packages/nodes-base/nodes/Google/Drive/v2/actions/drive/update.operation.ts
new file mode 100644
index 0000000000..04e51cfa06
--- /dev/null
+++ b/packages/nodes-base/nodes/Google/Drive/v2/actions/drive/update.operation.ts
@@ -0,0 +1,116 @@
+import type { IExecuteFunctions } from 'n8n-core';
+import type { IDataObject, INodeExecutionData, INodeProperties } from 'n8n-workflow';
+
+import { updateDisplayOptions } from '../../../../../../utils/utilities';
+import { googleApiRequest } from '../../transport';
+import { sharedDriveRLC } from '../common.descriptions';
+
+const properties: INodeProperties[] = [
+ {
+ ...sharedDriveRLC,
+ description: 'The shared drive to update',
+ },
+ {
+ displayName: 'Update Fields',
+ name: 'options',
+ type: 'collection',
+ placeholder: 'Add Option',
+ default: {},
+ displayOptions: {
+ show: {
+ operation: ['update'],
+ resource: ['drive'],
+ },
+ },
+ options: [
+ {
+ displayName: 'Color RGB',
+ name: 'colorRgb',
+ type: 'color',
+ default: '',
+ description: 'The color of this shared drive as an RGB hex string',
+ },
+ {
+ displayName: 'Name',
+ name: 'name',
+ type: 'string',
+ default: '',
+ description: 'The updated name of the shared drive',
+ },
+ {
+ displayName: 'Restrictions',
+ name: 'restrictions',
+ type: 'collection',
+ placeholder: 'Add Field',
+ default: {},
+ options: [
+ {
+ displayName: 'Admin Managed Restrictions',
+ name: 'adminManagedRestrictions',
+ type: 'boolean',
+ default: false,
+ description:
+ 'Whether the options to copy, print, or download files inside this shared drive, should be disabled for readers and commenters. When this restriction is set to true, it will override the similarly named field to true for any file inside this shared drive.',
+ },
+ {
+ displayName: 'Copy Requires Writer Permission',
+ name: 'copyRequiresWriterPermission',
+ type: 'boolean',
+ default: false,
+ description:
+ 'Whether the options to copy, print, or download files inside this shared drive, should be disabled for readers and commenters. When this restriction is set to true, it will override the similarly named field to true for any file inside this shared drive.',
+ },
+ {
+ displayName: 'Domain Users Only',
+ name: 'domainUsersOnly',
+ type: 'boolean',
+ default: false,
+ description:
+ 'Whether access to this shared drive and items inside this shared drive is restricted to users of the domain to which this shared drive belongs. This restriction may be overridden by other sharing policies controlled outside of this shared drive.',
+ },
+ {
+ displayName: 'Drive Members Only',
+ name: 'driveMembersOnly',
+ type: 'boolean',
+ default: false,
+ description:
+ 'Whether access to items inside this shared drive is restricted to its members',
+ },
+ ],
+ },
+ ],
+ },
+];
+
+const displayOptions = {
+ show: {
+ resource: ['drive'],
+ operation: ['update'],
+ },
+};
+
+export const description = updateDisplayOptions(displayOptions, properties);
+
+export async function execute(this: IExecuteFunctions, i: number): Promise {
+ const returnData: INodeExecutionData[] = [];
+ const options = this.getNodeParameter('options', i, {});
+
+ const driveId = this.getNodeParameter('driveId', i, undefined, {
+ extractValue: true,
+ }) as string;
+
+ const body: IDataObject = {};
+
+ Object.assign(body, options);
+
+ const response = await googleApiRequest.call(this, 'PATCH', `/drive/v3/drives/${driveId}`, body);
+
+ const executionData = this.helpers.constructExecutionMetaData(
+ this.helpers.returnJsonArray(response as IDataObject[]),
+ { itemData: { item: i } },
+ );
+
+ returnData.push(...executionData);
+
+ return returnData;
+}
diff --git a/packages/nodes-base/nodes/Google/Drive/v2/actions/file/File.resource.ts b/packages/nodes-base/nodes/Google/Drive/v2/actions/file/File.resource.ts
new file mode 100644
index 0000000000..2c7b34b3c3
--- /dev/null
+++ b/packages/nodes-base/nodes/Google/Drive/v2/actions/file/File.resource.ts
@@ -0,0 +1,85 @@
+import type { INodeProperties } from 'n8n-workflow';
+
+import * as copy from './copy.operation';
+import * as createFromText from './createFromText.operation';
+import * as deleteFile from './deleteFile.operation';
+import * as download from './download.operation';
+import * as move from './move.operation';
+import * as share from './share.operation';
+import * as update from './update.operation';
+import * as upload from './upload.operation';
+
+export { copy, createFromText, deleteFile, download, move, share, update, upload };
+
+export const description: INodeProperties[] = [
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ noDataExpression: true,
+ displayOptions: {
+ show: {
+ resource: ['file'],
+ },
+ },
+ options: [
+ {
+ name: 'Copy',
+ value: 'copy',
+ description: 'Create a copy of an existing file',
+ action: 'Copy file',
+ },
+ {
+ name: 'Create From Text',
+ value: 'createFromText',
+ description: 'Create a file from a provided text',
+ action: 'Create file from text',
+ },
+ {
+ name: 'Delete',
+ value: 'deleteFile',
+ description: 'Permanently delete a file',
+ action: 'Delete a file',
+ },
+ {
+ name: 'Download',
+ value: 'download',
+ description: 'Download a file',
+ action: 'Download file',
+ },
+ {
+ name: 'Move',
+ value: 'move',
+ description: 'Move a file to another folder',
+ action: 'Move file',
+ },
+ {
+ name: 'Share',
+ value: 'share',
+ description: 'Add sharing permissions to a file',
+ action: 'Share file',
+ },
+ {
+ name: 'Update',
+ value: 'update',
+ description: 'Update a file',
+ action: 'Update file',
+ },
+ {
+ name: 'Upload',
+ value: 'upload',
+ description: 'Upload an existing file to Google Drive',
+ action: 'Upload file',
+ },
+ ],
+ default: 'upload',
+ },
+ ...copy.description,
+ ...deleteFile.description,
+ ...createFromText.description,
+ ...download.description,
+ ...move.description,
+ ...share.description,
+ ...update.description,
+ ...upload.description,
+];
diff --git a/packages/nodes-base/nodes/Google/Drive/v2/actions/file/copy.operation.ts b/packages/nodes-base/nodes/Google/Drive/v2/actions/file/copy.operation.ts
new file mode 100644
index 0000000000..20cad67780
--- /dev/null
+++ b/packages/nodes-base/nodes/Google/Drive/v2/actions/file/copy.operation.ts
@@ -0,0 +1,136 @@
+import type { IExecuteFunctions } from 'n8n-core';
+import type {
+ IDataObject,
+ INodeExecutionData,
+ INodeParameterResourceLocator,
+ INodeProperties,
+} from 'n8n-workflow';
+
+import { updateDisplayOptions } from '../../../../../../utils/utilities';
+import { googleApiRequest } from '../../transport';
+import { driveRLC, fileRLC, folderRLC } from '../common.descriptions';
+import { setParentFolder } from '../../helpers/utils';
+
+const properties: INodeProperties[] = [
+ {
+ ...fileRLC,
+ description: 'The file to copy',
+ },
+ {
+ displayName: 'File Name',
+ name: 'name',
+ type: 'string',
+ default: '',
+ placeholder: 'e.g. My File',
+ description:
+ 'The name of the new file. If not set, “Copy of {original file name}” will be used.',
+ },
+ {
+ displayName: 'Copy In The Same Folder',
+ name: 'sameFolder',
+ type: 'boolean',
+ default: true,
+ description: 'Whether to copy the file in the same folder as the original file',
+ },
+ {
+ ...driveRLC,
+ displayName: 'Parent Drive',
+ description: 'The drive where to save the copied file',
+ displayOptions: { show: { sameFolder: [false] } },
+ },
+ {
+ ...folderRLC,
+ displayName: 'Parent Folder',
+ description: 'The folder where to save the copied file',
+ displayOptions: { show: { sameFolder: [false] } },
+ },
+ {
+ displayName: 'Options',
+ name: 'options',
+ type: 'collection',
+ placeholder: 'Add Option',
+ default: {},
+ options: [
+ {
+ displayName: 'Copy Requires Writer Permission',
+ name: 'copyRequiresWriterPermission',
+ type: 'boolean',
+ default: false,
+ description:
+ 'Whether the options to copy, print, or download this file, should be disabled for readers and commenters',
+ },
+ {
+ displayName: 'Description',
+ name: 'description',
+ type: 'string',
+ default: '',
+ description: 'A short description of the file',
+ },
+ ],
+ },
+];
+
+const displayOptions = {
+ show: {
+ resource: ['file'],
+ operation: ['copy'],
+ },
+};
+
+export const description = updateDisplayOptions(displayOptions, properties);
+
+export async function execute(this: IExecuteFunctions, i: number): Promise {
+ const file = this.getNodeParameter('fileId', i) as INodeParameterResourceLocator;
+
+ const fileId = file.value;
+
+ const options = this.getNodeParameter('options', i, {});
+
+ let name = this.getNodeParameter('name', i) as string;
+ name = name ? name : `Copy of ${file.cachedResultName}`;
+
+ const copyRequiresWriterPermission = options.copyRequiresWriterPermission || false;
+
+ const qs = {
+ includeItemsFromAllDrives: true,
+ supportsAllDrives: true,
+ spaces: 'appDataFolder, drive',
+ corpora: 'allDrives',
+ };
+
+ const parents: string[] = [];
+ const sameFolder = this.getNodeParameter('sameFolder', i) as boolean;
+
+ if (!sameFolder) {
+ const driveId = this.getNodeParameter('driveId', i, undefined, {
+ extractValue: true,
+ }) as string;
+
+ const folderId = this.getNodeParameter('folderId', i, undefined, {
+ extractValue: true,
+ }) as string;
+
+ parents.push(setParentFolder(folderId, driveId));
+ }
+
+ const body: IDataObject = { copyRequiresWriterPermission, parents, name };
+
+ if (options.description) {
+ body.description = options.description;
+ }
+
+ const response = await googleApiRequest.call(
+ this,
+ 'POST',
+ `/drive/v3/files/${fileId}/copy`,
+ body,
+ qs,
+ );
+
+ const executionData = this.helpers.constructExecutionMetaData(
+ this.helpers.returnJsonArray(response as IDataObject[]),
+ { itemData: { item: i } },
+ );
+
+ return executionData;
+}
diff --git a/packages/nodes-base/nodes/Google/Drive/v2/actions/file/createFromText.operation.ts b/packages/nodes-base/nodes/Google/Drive/v2/actions/file/createFromText.operation.ts
new file mode 100644
index 0000000000..2d109c51ca
--- /dev/null
+++ b/packages/nodes-base/nodes/Google/Drive/v2/actions/file/createFromText.operation.ts
@@ -0,0 +1,183 @@
+import type { IExecuteFunctions } from 'n8n-core';
+import type { IDataObject, INodeExecutionData, INodeProperties } from 'n8n-workflow';
+
+import { updateDisplayOptions } from '../../../../../../utils/utilities';
+import { driveRLC, folderRLC, updateCommonOptions } from '../common.descriptions';
+import { googleApiRequest } from '../../transport';
+import { DRIVE } from '../../helpers/interfaces';
+import { setFileProperties, setParentFolder, setUpdateCommonParams } from '../../helpers/utils';
+
+const properties: INodeProperties[] = [
+ {
+ displayName: 'File Content',
+ name: 'content',
+ type: 'string',
+ default: '',
+ typeOptions: {
+ rows: 2,
+ },
+ description: 'The text to create the file with',
+ },
+ {
+ displayName: 'File Name',
+ name: 'name',
+ type: 'string',
+ default: '',
+ placeholder: 'e.g. My New File',
+ description:
+ "The name of the file you want to create. If not specified, 'Untitled' will be used.",
+ },
+ {
+ ...driveRLC,
+ displayName: 'Parent Drive',
+ required: false,
+ description: 'The drive where to create the new file',
+ },
+ {
+ ...folderRLC,
+ displayName: 'Parent Folder',
+ required: false,
+ description: 'The folder where to create the new file',
+ },
+ {
+ displayName: 'Options',
+ name: 'options',
+ type: 'collection',
+ placeholder: 'Add Option',
+ default: {},
+ options: [
+ ...updateCommonOptions,
+ {
+ displayName: 'Convert to Google Document',
+ name: 'convertToGoogleDocument',
+ type: 'boolean',
+ default: false,
+ description: 'Whether to create a Google Document (instead of the .txt default format)',
+ hint: 'Google Docs API has to be enabled in the Google API Console.',
+ },
+ ],
+ },
+];
+
+const displayOptions = {
+ show: {
+ resource: ['file'],
+ operation: ['createFromText'],
+ },
+};
+
+export const description = updateDisplayOptions(displayOptions, properties);
+
+export async function execute(this: IExecuteFunctions, i: number): Promise {
+ const name = (this.getNodeParameter('name', i) as string) || 'Untitled';
+
+ const options = this.getNodeParameter('options', i, {});
+ const convertToGoogleDocument = (options.convertToGoogleDocument as boolean) || false;
+ const mimeType = convertToGoogleDocument ? DRIVE.DOCUMENT : 'text/plain';
+
+ const driveId = this.getNodeParameter('driveId', i, undefined, {
+ extractValue: true,
+ }) as string;
+
+ const folderId = this.getNodeParameter('folderId', i, undefined, {
+ extractValue: true,
+ }) as string;
+
+ const bodyParameters = setFileProperties(
+ {
+ name,
+ parents: [setParentFolder(folderId, driveId)],
+ mimeType,
+ },
+ options,
+ );
+
+ const boundary = 'XXXXXX';
+
+ const qs = setUpdateCommonParams(
+ {
+ includeItemsFromAllDrives: true,
+ supportsAllDrives: true,
+ spaces: 'appDataFolder, drive',
+ corpora: 'allDrives',
+ },
+ options,
+ );
+
+ let response;
+ if (convertToGoogleDocument) {
+ const document = await googleApiRequest.call(
+ this,
+ 'POST',
+ '/drive/v3/files',
+ bodyParameters,
+ qs,
+ );
+
+ const text = this.getNodeParameter('content', i, '') as string;
+
+ const body = {
+ requests: [
+ {
+ insertText: {
+ text,
+ endOfSegmentLocation: {
+ segmentId: '', //empty segment ID signifies the document's body
+ },
+ },
+ },
+ ],
+ };
+
+ const updateResponse = await googleApiRequest.call(
+ this,
+ 'POST',
+ '',
+ body,
+ undefined,
+ `https://docs.googleapis.com/v1/documents/${document.id}:batchUpdate`,
+ );
+
+ response = { id: updateResponse.documentId };
+ } else {
+ const content = Buffer.from(this.getNodeParameter('content', i, '') as string, 'utf8');
+ const contentLength = content.byteLength;
+
+ const body = `
+ \n--${boundary}\
+ \nContent-Type: application/json; charset=UTF-8\
+ \n\n${JSON.stringify(bodyParameters)}\
+ \n--${boundary}\
+ \nContent-Type: text/plain\
+ \nContent-Transfer-Encoding: base64\
+ \n\n${content}\
+ \n--${boundary}--`;
+
+ const responseData = await googleApiRequest.call(
+ this,
+ 'POST',
+ '/upload/drive/v3/files',
+ body,
+ {
+ uploadType: 'multipart',
+ ...qs,
+ },
+ undefined,
+ {
+ headers: {
+ 'Content-Type': `multipart/related; boundary=${boundary}`,
+ 'Content-Length': contentLength,
+ },
+ },
+ );
+
+ response = { id: responseData.id };
+ }
+
+ const executionData = this.helpers.constructExecutionMetaData(
+ this.helpers.returnJsonArray(response as IDataObject),
+ { itemData: { item: i } },
+ );
+
+ return executionData;
+}
diff --git a/packages/nodes-base/nodes/Google/Drive/v2/actions/file/deleteFile.operation.ts b/packages/nodes-base/nodes/Google/Drive/v2/actions/file/deleteFile.operation.ts
new file mode 100644
index 0000000000..3d76965e39
--- /dev/null
+++ b/packages/nodes-base/nodes/Google/Drive/v2/actions/file/deleteFile.operation.ts
@@ -0,0 +1,67 @@
+import type { IExecuteFunctions } from 'n8n-core';
+import type { INodeExecutionData, INodeProperties } from 'n8n-workflow';
+
+import { updateDisplayOptions } from '../../../../../../utils/utilities';
+import { googleApiRequest } from '../../transport';
+import { fileRLC } from '../common.descriptions';
+
+const properties: INodeProperties[] = [
+ {
+ ...fileRLC,
+ description: 'The file to delete',
+ },
+ {
+ displayName: 'Options',
+ name: 'options',
+ type: 'collection',
+ placeholder: 'Add Option',
+ default: {},
+ options: [
+ {
+ displayName: 'Delete Permanently',
+ name: 'deletePermanently',
+ type: 'boolean',
+ default: false,
+ description:
+ 'Whether to delete the file immediately. If false, the file will be moved to the trash.',
+ },
+ ],
+ },
+];
+
+const displayOptions = {
+ show: {
+ resource: ['file'],
+ operation: ['deleteFile'],
+ },
+};
+
+export const description = updateDisplayOptions(displayOptions, properties);
+
+export async function execute(this: IExecuteFunctions, i: number): Promise {
+ const fileId = this.getNodeParameter('fileId', i, undefined, {
+ extractValue: true,
+ }) as string;
+
+ const deletePermanently = this.getNodeParameter('options.deletePermanently', i, false) as boolean;
+
+ const qs = {
+ supportsAllDrives: true,
+ };
+
+ if (deletePermanently) {
+ await googleApiRequest.call(this, 'DELETE', `/drive/v3/files/${fileId}`, undefined, qs);
+ } else {
+ await googleApiRequest.call(this, 'PATCH', `/drive/v3/files/${fileId}`, { trashed: true }, qs);
+ }
+
+ const executionData = this.helpers.constructExecutionMetaData(
+ this.helpers.returnJsonArray({
+ id: fileId,
+ success: true,
+ }),
+ { itemData: { item: i } },
+ );
+
+ return executionData;
+}
diff --git a/packages/nodes-base/nodes/Google/Drive/v2/actions/file/download.operation.ts b/packages/nodes-base/nodes/Google/Drive/v2/actions/file/download.operation.ts
new file mode 100644
index 0000000000..47341f5f30
--- /dev/null
+++ b/packages/nodes-base/nodes/Google/Drive/v2/actions/file/download.operation.ts
@@ -0,0 +1,284 @@
+import type { IExecuteFunctions } from 'n8n-core';
+import type {
+ IBinaryKeyData,
+ IDataObject,
+ INodeExecutionData,
+ INodeProperties,
+} from 'n8n-workflow';
+
+import { updateDisplayOptions } from '../../../../../../utils/utilities';
+import { googleApiRequest } from '../../transport';
+import { fileRLC } from '../common.descriptions';
+
+const properties: INodeProperties[] = [
+ {
+ ...fileRLC,
+ description: 'The file to download',
+ },
+ {
+ displayName: 'Options',
+ name: 'options',
+ type: 'collection',
+ placeholder: 'Add Option',
+ default: {},
+ options: [
+ {
+ displayName: 'Binary Property',
+ name: 'binaryPropertyName',
+ type: 'string',
+ placeholder: 'e.g. data',
+ default: 'data',
+ description: 'Use this field name in the following nodes, to use the binary file data',
+ hint: 'The name of the output field to put the binary file data in',
+ },
+ {
+ displayName: 'Google File Conversion',
+ name: 'googleFileConversion',
+ type: 'fixedCollection',
+ typeOptions: {
+ multipleValues: false,
+ },
+ default: {},
+ placeholder: 'Add Conversion',
+ options: [
+ {
+ displayName: 'Conversion',
+ name: 'conversion',
+ values: [
+ {
+ displayName: 'Google Docs',
+ name: 'docsToFormat',
+ type: 'options',
+ // eslint-disable-next-line n8n-nodes-base/node-param-options-type-unsorted-items
+ options: [
+ {
+ name: 'HTML',
+ value: 'text/html',
+ },
+ {
+ name: 'MS Word Document',
+ value:
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
+ },
+ {
+ name: 'Open Office Document',
+ value: 'application/vnd.oasis.opendocument.text',
+ },
+ {
+ name: 'PDF',
+ value: 'application/pdf',
+ },
+ {
+ // eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
+ name: 'Rich Text (rtf)',
+ value: 'application/rtf',
+ },
+ {
+ // eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
+ name: 'Text (txt)',
+ value: 'text/plain',
+ },
+ ],
+ default: 'text/html',
+ description: 'Format used to export when downloading Google Docs files',
+ },
+ {
+ displayName: 'Google Drawings',
+ name: 'drawingsToFormat',
+ type: 'options',
+ options: [
+ {
+ name: 'JPEG',
+ value: 'image/jpeg',
+ },
+ {
+ name: 'PDF',
+ value: 'application/pdf',
+ },
+ {
+ name: 'PNG',
+ value: 'image/png',
+ },
+ {
+ name: 'SVG',
+ value: 'image/svg+xml',
+ },
+ ],
+ default: 'image/jpeg',
+ description: 'Format used to export when downloading Google Drawings files',
+ },
+ {
+ displayName: 'Google Slides',
+ name: 'slidesToFormat',
+ type: 'options',
+ options: [
+ {
+ name: 'MS PowerPoint',
+ value:
+ 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
+ },
+ {
+ name: 'OpenOffice Presentation',
+ value: 'application/vnd.oasis.opendocument.presentation',
+ },
+ {
+ name: 'PDF',
+ value: 'application/pdf',
+ },
+ ],
+ default:
+ 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
+ description: 'Format used to export when downloading Google Slides files',
+ },
+ {
+ displayName: 'Google Sheets',
+ name: 'sheetsToFormat',
+ type: 'options',
+ options: [
+ {
+ name: 'CSV',
+ value: 'text/csv',
+ },
+ {
+ name: 'MS Excel',
+ value: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
+ },
+ {
+ name: 'Open Office Sheet',
+ value: 'application/vnd.oasis.opendocument.spreadsheet',
+ },
+ {
+ name: 'PDF',
+ value: 'application/pdf',
+ },
+ ],
+ default: 'text/csv',
+ description: 'Format used to export when downloading Google Sheets files',
+ },
+ ],
+ },
+ ],
+ },
+ {
+ displayName: 'File Name',
+ name: 'fileName',
+ type: 'string',
+ default: '',
+ description: 'File name. Ex: data.pdf.',
+ },
+ ],
+ },
+];
+
+const displayOptions = {
+ show: {
+ resource: ['file'],
+ operation: ['download'],
+ },
+};
+
+export const description = updateDisplayOptions(displayOptions, properties);
+
+export async function execute(
+ this: IExecuteFunctions,
+ i: number,
+ item: INodeExecutionData,
+): Promise {
+ const fileId = this.getNodeParameter('fileId', i, undefined, {
+ extractValue: true,
+ }) as string;
+
+ const downloadOptions = this.getNodeParameter('options', i);
+
+ const requestOptions = {
+ useStream: true,
+ resolveWithFullResponse: true,
+ encoding: null,
+ json: false,
+ };
+
+ const file = await googleApiRequest.call(
+ this,
+ 'GET',
+ `/drive/v3/files/${fileId}`,
+ {},
+ { fields: 'mimeType,name', supportsTeamDrives: true },
+ );
+ let response;
+
+ if (file.mimeType?.includes('vnd.google-apps')) {
+ const parameterKey = 'options.googleFileConversion.conversion';
+ const type = file.mimeType.split('.')[2];
+ let mime;
+ if (type === 'document') {
+ mime = this.getNodeParameter(
+ `${parameterKey}.docsToFormat`,
+ i,
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
+ ) as string;
+ } else if (type === 'presentation') {
+ mime = this.getNodeParameter(
+ `${parameterKey}.slidesToFormat`,
+ i,
+ 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
+ ) as string;
+ } else if (type === 'spreadsheet') {
+ mime = this.getNodeParameter(
+ `${parameterKey}.sheetsToFormat`,
+ i,
+ 'application/x-vnd.oasis.opendocument.spreadsheet',
+ ) as string;
+ } else {
+ mime = this.getNodeParameter(`${parameterKey}.drawingsToFormat`, i, 'image/jpeg') as string;
+ }
+ response = await googleApiRequest.call(
+ this,
+ 'GET',
+ `/drive/v3/files/${fileId}/export`,
+ {},
+ { mimeType: mime },
+ undefined,
+ requestOptions,
+ );
+ } else {
+ response = await googleApiRequest.call(
+ this,
+ 'GET',
+ `/drive/v3/files/${fileId}`,
+ {},
+ { alt: 'media' },
+ undefined,
+ requestOptions,
+ );
+ }
+
+ const mimeType =
+ (response.headers as IDataObject)?.['content-type'] ?? file.mimeType ?? undefined;
+ const fileName = downloadOptions.fileName ?? file.name ?? undefined;
+
+ const newItem: INodeExecutionData = {
+ json: item.json,
+ binary: {},
+ };
+
+ if (item.binary !== undefined) {
+ // Create a shallow copy of the binary data so that the old
+ // data references which do not get changed still stay behind
+ // but the incoming data does not get changed.
+ Object.assign(newItem.binary as IBinaryKeyData, item.binary);
+ }
+
+ item = newItem;
+
+ const dataPropertyNameDownload = (downloadOptions.binaryPropertyName as string) || 'data';
+
+ item.binary![dataPropertyNameDownload] = await this.helpers.prepareBinaryData(
+ response.body as Buffer,
+ fileName as string,
+ mimeType as string,
+ );
+
+ const executionData = this.helpers.constructExecutionMetaData([item], { itemData: { item: i } });
+
+ return executionData;
+}
diff --git a/packages/nodes-base/nodes/Google/Drive/v2/actions/file/move.operation.ts b/packages/nodes-base/nodes/Google/Drive/v2/actions/file/move.operation.ts
new file mode 100644
index 0000000000..e2b4aac1d5
--- /dev/null
+++ b/packages/nodes-base/nodes/Google/Drive/v2/actions/file/move.operation.ts
@@ -0,0 +1,84 @@
+import type { IExecuteFunctions } from 'n8n-core';
+import type { IDataObject, INodeExecutionData, INodeProperties } from 'n8n-workflow';
+
+import { updateDisplayOptions } from '../../../../../../utils/utilities';
+import { driveRLC, fileRLC, folderRLC } from '../common.descriptions';
+import { googleApiRequest } from '../../transport';
+import { setParentFolder } from '../../helpers/utils';
+
+const properties: INodeProperties[] = [
+ {
+ ...fileRLC,
+ description: 'The file to move',
+ },
+ {
+ ...driveRLC,
+ displayName: 'Parent Drive',
+ description: 'The drive where to move the file',
+ },
+ {
+ ...folderRLC,
+ displayName: 'Parent Folder',
+ description: 'The folder where to move the file',
+ },
+];
+
+const displayOptions = {
+ show: {
+ resource: ['file'],
+ operation: ['move'],
+ },
+};
+
+export const description = updateDisplayOptions(displayOptions, properties);
+
+export async function execute(this: IExecuteFunctions, i: number): Promise {
+ const fileId = this.getNodeParameter('fileId', i, undefined, {
+ extractValue: true,
+ });
+
+ const driveId = this.getNodeParameter('driveId', i, undefined, {
+ extractValue: true,
+ }) as string;
+
+ const folderId = this.getNodeParameter('folderId', i, undefined, {
+ extractValue: true,
+ }) as string;
+
+ const qs = {
+ includeItemsFromAllDrives: true,
+ supportsAllDrives: true,
+ spaces: 'appDataFolder, drive',
+ corpora: 'allDrives',
+ };
+
+ const { parents } = await googleApiRequest.call(
+ this,
+ 'GET',
+ `/drive/v3/files/${fileId}`,
+ undefined,
+ {
+ ...qs,
+ fields: 'parents',
+ },
+ );
+
+ const response = await googleApiRequest.call(
+ this,
+ 'PATCH',
+ `/drive/v3/files/${fileId}`,
+ undefined,
+ {
+ ...qs,
+ addParents: setParentFolder(folderId, driveId),
+ removeParents: ((parents as string[]) || []).join(','),
+ },
+ );
+
+ const executionData = this.helpers.constructExecutionMetaData(
+ this.helpers.returnJsonArray(response as IDataObject[]),
+ { itemData: { item: i } },
+ );
+
+ return executionData;
+}
diff --git a/packages/nodes-base/nodes/Google/Drive/v2/actions/file/share.operation.ts b/packages/nodes-base/nodes/Google/Drive/v2/actions/file/share.operation.ts
new file mode 100644
index 0000000000..9db3dd3882
--- /dev/null
+++ b/packages/nodes-base/nodes/Google/Drive/v2/actions/file/share.operation.ts
@@ -0,0 +1,64 @@
+import type { IExecuteFunctions } from 'n8n-core';
+import type { IDataObject, INodeExecutionData, INodeProperties } from 'n8n-workflow';
+
+import { updateDisplayOptions } from '../../../../../../utils/utilities';
+import { googleApiRequest } from '../../transport';
+import { fileRLC, permissionsOptions, shareOptions } from '../common.descriptions';
+
+const properties: INodeProperties[] = [
+ {
+ ...fileRLC,
+ description: 'The file to share',
+ },
+ permissionsOptions,
+ shareOptions,
+];
+
+const displayOptions = {
+ show: {
+ resource: ['file'],
+ operation: ['share'],
+ },
+};
+
+export const description = updateDisplayOptions(displayOptions, properties);
+
+export async function execute(this: IExecuteFunctions, i: number): Promise {
+ const returnData: INodeExecutionData[] = [];
+
+ const fileId = this.getNodeParameter('fileId', i, undefined, {
+ extractValue: true,
+ }) as string;
+
+ const permissions = this.getNodeParameter('permissionsUi', i) as IDataObject;
+
+ const shareOption = this.getNodeParameter('options', i);
+
+ const body: IDataObject = {};
+
+ const qs: IDataObject = {
+ supportsAllDrives: true,
+ };
+
+ if (permissions.permissionsValues) {
+ Object.assign(body, permissions.permissionsValues);
+ }
+
+ Object.assign(qs, shareOption);
+
+ const response = await googleApiRequest.call(
+ this,
+ 'POST',
+ `/drive/v3/files/${fileId}/permissions`,
+ body,
+ qs,
+ );
+
+ const executionData = this.helpers.constructExecutionMetaData(
+ this.helpers.returnJsonArray(response as IDataObject[]),
+ { itemData: { item: i } },
+ );
+ returnData.push(...executionData);
+
+ return returnData;
+}
diff --git a/packages/nodes-base/nodes/Google/Drive/v2/actions/file/update.operation.ts b/packages/nodes-base/nodes/Google/Drive/v2/actions/file/update.operation.ts
new file mode 100644
index 0000000000..3e5c8a595a
--- /dev/null
+++ b/packages/nodes-base/nodes/Google/Drive/v2/actions/file/update.operation.ts
@@ -0,0 +1,274 @@
+import type { IExecuteFunctions } from 'n8n-core';
+import type { IDataObject, INodeExecutionData, INodeProperties } from 'n8n-workflow';
+import { NodeOperationError } from 'n8n-workflow';
+
+import { updateDisplayOptions } from '../../../../../../utils/utilities';
+import {
+ getItemBinaryData,
+ prepareQueryString,
+ setFileProperties,
+ setUpdateCommonParams,
+} from '../../helpers/utils';
+import { googleApiRequest } from '../../transport';
+import { fileRLC, updateCommonOptions } from '../common.descriptions';
+
+const properties: INodeProperties[] = [
+ {
+ ...fileRLC,
+ displayName: 'File to Update',
+ description: 'The file to update',
+ },
+ {
+ displayName: 'Change File Content',
+ name: 'changeFileContent',
+ type: 'boolean',
+ default: false,
+ description: 'Whether to send a new binary data to update the file',
+ },
+ {
+ displayName: 'Input Data Field Name',
+ name: 'inputDataFieldName',
+ type: 'string',
+ placeholder: 'e.g. data',
+ default: 'data',
+ hint: 'The name of the input field containing the binary file data to update the file',
+ description:
+ 'Find the name of input field containing the binary data to update the file in the Input panel on the left, in the Binary tab',
+ displayOptions: {
+ show: {
+ changeFileContent: [true],
+ },
+ },
+ },
+ {
+ displayName: 'New Updated File Name',
+ name: 'newUpdatedFileName',
+ type: 'string',
+ default: '',
+ placeholder: 'e.g. My New File',
+ description: 'If not specified, the file name will not be changed',
+ },
+
+ {
+ displayName: 'Options',
+ name: 'options',
+ type: 'collection',
+ placeholder: 'Add Option',
+ default: {},
+ options: [
+ ...updateCommonOptions,
+ {
+ displayName: 'Move to Trash',
+ name: 'trashed',
+ type: 'boolean',
+ default: false,
+ description: 'Whether to move a file to the trash. Only the owner may trash a file.',
+ },
+ {
+ displayName: 'Return Fields',
+ name: 'fields',
+ type: 'multiOptions',
+ options: [
+ {
+ name: '[All]',
+ value: '*',
+ description: 'All fields',
+ },
+ {
+ name: 'explicitlyTrashed',
+ value: 'explicitlyTrashed',
+ },
+ {
+ name: 'exportLinks',
+ value: 'exportLinks',
+ },
+ {
+ name: 'hasThumbnail',
+ value: 'hasThumbnail',
+ },
+ {
+ name: 'iconLink',
+ value: 'iconLink',
+ },
+ {
+ name: 'ID',
+ value: 'id',
+ },
+ {
+ name: 'Kind',
+ value: 'kind',
+ },
+ {
+ name: 'mimeType',
+ value: 'mimeType',
+ },
+ {
+ name: 'Name',
+ value: 'name',
+ },
+ {
+ name: 'Permissions',
+ value: 'permissions',
+ },
+ {
+ name: 'Shared',
+ value: 'shared',
+ },
+ {
+ name: 'Spaces',
+ value: 'spaces',
+ },
+ {
+ name: 'Starred',
+ value: 'starred',
+ },
+ {
+ name: 'thumbnailLink',
+ value: 'thumbnailLink',
+ },
+ {
+ name: 'Trashed',
+ value: 'trashed',
+ },
+ {
+ name: 'Version',
+ value: 'version',
+ },
+ {
+ name: 'webViewLink',
+ value: 'webViewLink',
+ },
+ ],
+ default: [],
+ description: 'The fields to return',
+ },
+ ],
+ },
+];
+
+const displayOptions = {
+ show: {
+ resource: ['file'],
+ operation: ['update'],
+ },
+};
+
+export const description = updateDisplayOptions(displayOptions, properties);
+
+export async function execute(this: IExecuteFunctions, i: number): Promise {
+ const fileId = this.getNodeParameter('fileId', i, undefined, {
+ extractValue: true,
+ }) as string;
+
+ const changeFileContent = this.getNodeParameter('changeFileContent', i, false) as boolean;
+
+ let mimeType;
+
+ // update file binary data
+ if (changeFileContent) {
+ const inputDataFieldName = this.getNodeParameter('inputDataFieldName', i) as string;
+
+ const binaryData = await getItemBinaryData.call(this, inputDataFieldName, i);
+
+ const { contentLength, fileContent } = binaryData;
+ mimeType = binaryData.mimeType;
+
+ if (Buffer.isBuffer(fileContent)) {
+ await googleApiRequest.call(
+ this,
+ 'PATCH',
+ `/upload/drive/v3/files/${fileId}`,
+ fileContent,
+ {
+ uploadType: 'media',
+ },
+ undefined,
+ {
+ headers: {
+ 'Content-Type': mimeType,
+ 'Content-Length': contentLength,
+ },
+ },
+ );
+ } else {
+ const resumableUpload = await googleApiRequest.call(
+ this,
+ 'PATCH',
+ `/upload/drive/v3/files/${fileId}`,
+ undefined,
+ { uploadType: 'resumable' },
+ undefined,
+ {
+ resolveWithFullResponse: true,
+ },
+ );
+ const uploadUrl = resumableUpload.headers.location;
+
+ let offset = 0;
+ for await (const chunk of fileContent) {
+ const nextOffset = offset + Number(chunk.length);
+ try {
+ await this.helpers.httpRequest({
+ method: 'PUT',
+ url: uploadUrl,
+ headers: {
+ 'Content-Length': chunk.length,
+ 'Content-Range': `bytes ${offset}-${nextOffset - 1}/${contentLength}`,
+ },
+ body: chunk,
+ });
+ } catch (error) {
+ if (error.response?.status !== 308) {
+ throw new NodeOperationError(this.getNode(), error as Error, { itemIndex: i });
+ }
+ }
+ offset = nextOffset;
+ }
+ }
+ }
+
+ const options = this.getNodeParameter('options', i, {});
+
+ const qs: IDataObject = setUpdateCommonParams(
+ {
+ supportsAllDrives: true,
+ },
+ options,
+ );
+
+ if (options.fields) {
+ const queryFields = prepareQueryString(options.fields as string[]);
+ qs.fields = queryFields;
+ }
+
+ if (options.trashed) {
+ qs.trashed = options.trashed;
+ }
+
+ const body: IDataObject = setFileProperties({}, options);
+
+ const newUpdatedFileName = this.getNodeParameter('newUpdatedFileName', i, '') as string;
+ if (newUpdatedFileName) {
+ body.name = newUpdatedFileName;
+ }
+
+ if (mimeType) {
+ body.mimeType = mimeType;
+ }
+
+ // update file metadata
+ const responseData = await googleApiRequest.call(
+ this,
+ 'PATCH',
+ `/drive/v3/files/${fileId}`,
+ body,
+ qs,
+ );
+
+ const executionData = this.helpers.constructExecutionMetaData(
+ this.helpers.returnJsonArray(responseData as IDataObject[]),
+ { itemData: { item: i } },
+ );
+
+ return executionData;
+}
diff --git a/packages/nodes-base/nodes/Google/Drive/v2/actions/file/upload.operation.ts b/packages/nodes-base/nodes/Google/Drive/v2/actions/file/upload.operation.ts
new file mode 100644
index 0000000000..0ed1107d96
--- /dev/null
+++ b/packages/nodes-base/nodes/Google/Drive/v2/actions/file/upload.operation.ts
@@ -0,0 +1,189 @@
+import type { IExecuteFunctions } from 'n8n-core';
+import type { IDataObject, INodeExecutionData, INodeProperties } from 'n8n-workflow';
+
+import { updateDisplayOptions } from '../../../../../../utils/utilities';
+import { googleApiRequest } from '../../transport';
+import { driveRLC, folderRLC, updateCommonOptions } from '../common.descriptions';
+import {
+ getItemBinaryData,
+ setFileProperties,
+ setUpdateCommonParams,
+ setParentFolder,
+} from '../../helpers/utils';
+
+const properties: INodeProperties[] = [
+ {
+ displayName: 'Input Data Field Name',
+ name: 'inputDataFieldName',
+ type: 'string',
+ placeholder: '“e.g. data',
+ default: 'data',
+ required: true,
+ hint: 'The name of the input field containing the binary file data to update the file',
+ description:
+ 'Find the name of input field containing the binary data to update the file in the Input panel on the left, in the Binary tab',
+ },
+ {
+ displayName: 'File Name',
+ name: 'name',
+ type: 'string',
+ default: '',
+ placeholder: 'e.g. My New File',
+ description: 'If not specified, the original file name will be used',
+ },
+ {
+ ...driveRLC,
+ displayName: 'Parent Drive',
+ description: 'The drive where to upload the file',
+ },
+ {
+ ...folderRLC,
+ displayName: 'Parent Folder',
+ description: 'The folder where to upload the file',
+ },
+ {
+ displayName: 'Options',
+ name: 'options',
+ type: 'collection',
+ placeholder: 'Add Option',
+ default: {},
+ options: [
+ ...updateCommonOptions,
+ {
+ displayName: 'Simplify Output',
+ name: 'simplifyOutput',
+ type: 'boolean',
+ default: true,
+ description: 'Whether to return a simplified version of the response instead of all fields',
+ },
+ ],
+ },
+];
+
+const displayOptions = {
+ show: {
+ resource: ['file'],
+ operation: ['upload'],
+ },
+};
+
+export const description = updateDisplayOptions(displayOptions, properties);
+
+export async function execute(this: IExecuteFunctions, i: number): Promise {
+ const returnData: INodeExecutionData[] = [];
+
+ const inputDataFieldName = this.getNodeParameter('inputDataFieldName', i) as string;
+
+ const { contentLength, fileContent, originalFilename, mimeType } = await getItemBinaryData.call(
+ this,
+ inputDataFieldName,
+ i,
+ );
+
+ const name = (this.getNodeParameter('name', i) as string) || originalFilename;
+
+ const driveId = this.getNodeParameter('driveId', i, undefined, {
+ extractValue: true,
+ }) as string;
+
+ const folderId = this.getNodeParameter('folderId', i, undefined, {
+ extractValue: true,
+ }) as string;
+
+ let uploadId;
+ if (Buffer.isBuffer(fileContent)) {
+ const response = await googleApiRequest.call(
+ this,
+ 'POST',
+ '/upload/drive/v3/files',
+ fileContent,
+ {
+ uploadType: 'media',
+ },
+ undefined,
+ {
+ headers: {
+ 'Content-Type': mimeType,
+ 'Content-Length': contentLength,
+ },
+ },
+ );
+
+ uploadId = response.id;
+ } else {
+ const resumableUpload = await googleApiRequest.call(
+ this,
+ 'POST',
+ '/upload/drive/v3/files',
+ undefined,
+ { uploadType: 'resumable' },
+ undefined,
+ {
+ resolveWithFullResponse: true,
+ },
+ );
+ const uploadUrl = resumableUpload.headers.location;
+
+ let offset = 0;
+ for await (const chunk of fileContent) {
+ const nextOffset = offset + Number(chunk.length);
+ try {
+ const response = await this.helpers.httpRequest({
+ method: 'PUT',
+ url: uploadUrl,
+ headers: {
+ 'Content-Length': chunk.length,
+ 'Content-Range': `bytes ${offset}-${nextOffset - 1}/${contentLength}`,
+ },
+ body: chunk,
+ });
+ uploadId = response?.id;
+ } catch (error) {
+ if (error.response?.status !== 308) throw error;
+ }
+ offset = nextOffset;
+ }
+ }
+
+ const options = this.getNodeParameter('options', i, {});
+
+ const qs = setUpdateCommonParams(
+ {
+ addParents: setParentFolder(folderId, driveId),
+ includeItemsFromAllDrives: true,
+ supportsAllDrives: true,
+ spaces: 'appDataFolder, drive',
+ corpora: 'allDrives',
+ },
+ options,
+ );
+
+ if (!options.simplifyOutput) {
+ qs.fields = '*';
+ }
+
+ const body = setFileProperties(
+ {
+ mimeType,
+ name,
+ originalFilename,
+ },
+ options,
+ );
+
+ const response = await googleApiRequest.call(
+ this,
+ 'PATCH',
+ `/drive/v3/files/${uploadId}`,
+ body,
+ qs,
+ );
+
+ const executionData = this.helpers.constructExecutionMetaData(
+ this.helpers.returnJsonArray(response as IDataObject[]),
+ { itemData: { item: i } },
+ );
+ returnData.push(...executionData);
+
+ return returnData;
+}
diff --git a/packages/nodes-base/nodes/Google/Drive/v2/actions/fileFolder/FileFolder.resource.ts b/packages/nodes-base/nodes/Google/Drive/v2/actions/fileFolder/FileFolder.resource.ts
new file mode 100644
index 0000000000..94b3840762
--- /dev/null
+++ b/packages/nodes-base/nodes/Google/Drive/v2/actions/fileFolder/FileFolder.resource.ts
@@ -0,0 +1,29 @@
+import type { INodeProperties } from 'n8n-workflow';
+
+import * as search from './search.operation';
+
+export { search };
+
+export const description: INodeProperties[] = [
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ noDataExpression: true,
+ displayOptions: {
+ show: {
+ resource: ['fileFolder'],
+ },
+ },
+ options: [
+ {
+ name: 'Search',
+ value: 'search',
+ description: 'Search or list files and folders',
+ action: 'Search files and folders',
+ },
+ ],
+ default: 'search',
+ },
+ ...search.description,
+];
diff --git a/packages/nodes-base/nodes/Google/Drive/v2/actions/fileFolder/search.operation.ts b/packages/nodes-base/nodes/Google/Drive/v2/actions/fileFolder/search.operation.ts
new file mode 100644
index 0000000000..597b15cf72
--- /dev/null
+++ b/packages/nodes-base/nodes/Google/Drive/v2/actions/fileFolder/search.operation.ts
@@ -0,0 +1,359 @@
+import type { IExecuteFunctions } from 'n8n-core';
+import type { IDataObject, INodeExecutionData, INodeProperties } from 'n8n-workflow';
+
+import { updateDisplayOptions } from '../../../../../../utils/utilities';
+import { driveRLC, fileTypesOptions, folderRLC } from '../common.descriptions';
+import { googleApiRequest, googleApiRequestAllItems } from '../../transport';
+import { prepareQueryString, updateDriveScopes } from '../../helpers/utils';
+import type { SearchFilter } from '../../helpers/interfaces';
+import { DRIVE, RLC_FOLDER_DEFAULT } from '../../helpers/interfaces';
+
+const properties: INodeProperties[] = [
+ {
+ displayName: 'Search Method',
+ name: 'searchMethod',
+ type: 'options',
+ options: [
+ {
+ name: 'Search File/Folder Name',
+ value: 'name',
+ },
+ {
+ name: 'Advanced Search',
+ value: 'query',
+ },
+ ],
+ default: 'name',
+ description: 'Whether to search for the file/folder name or use a query string',
+ },
+ {
+ displayName: 'Search Query',
+ name: 'queryString',
+ type: 'string',
+ default: '',
+ displayOptions: {
+ show: {
+ searchMethod: ['name'],
+ },
+ },
+ placeholder: 'e.g. My File / My Folder',
+ description:
+ 'The name of the file or folder to search for. Returns also files and folders whose names partially match this search term.',
+ },
+ {
+ displayName: 'Query String',
+ name: 'queryString',
+ type: 'string',
+ default: '',
+ displayOptions: {
+ show: {
+ searchMethod: ['query'],
+ },
+ },
+ placeholder: "e.g. not name contains 'hello'",
+ description:
+ 'Use the Google query strings syntax to search for a specific set of files or folders. Learn more.',
+ },
+ {
+ displayName: 'Return All',
+ name: 'returnAll',
+ type: 'boolean',
+ default: false,
+ description: 'Whether to return all results or only up to a given limit',
+ },
+ {
+ displayName: 'Limit',
+ name: 'limit',
+ type: 'number',
+ default: 50,
+ description: 'Max number of results to return',
+ typeOptions: {
+ minValue: 1,
+ },
+ displayOptions: {
+ show: {
+ returnAll: [false],
+ },
+ },
+ },
+ {
+ displayName: 'Filter',
+ name: 'filter',
+ type: 'collection',
+ placeholder: 'Add Filter',
+ default: {},
+ options: [
+ {
+ ...driveRLC,
+ description:
+ 'The drive you want to search in. By default, the personal "My Drive" is used.',
+ required: false,
+ },
+ {
+ ...folderRLC,
+ description:
+ 'The folder you want to search in. By default, the root folder of the drive is used. If you select a folder other than the root folder, only the direct children will be included.',
+ required: false,
+ },
+ {
+ displayName: 'What to Search',
+ name: 'whatToSearch',
+ type: 'options',
+ default: 'all',
+ options: [
+ {
+ name: 'Files and Folders',
+ value: 'all',
+ },
+ {
+ name: 'Files',
+ value: 'files',
+ },
+ {
+ name: 'Folders',
+ value: 'folders',
+ },
+ ],
+ },
+ {
+ displayName: 'File Types',
+ name: 'fileTypes',
+ type: 'multiOptions',
+ default: [],
+ description: 'Return only items corresponding to the selected MIME types',
+ options: fileTypesOptions,
+ displayOptions: {
+ show: {
+ whatToSearch: ['all'],
+ },
+ },
+ },
+ {
+ displayName: 'File Types',
+ name: 'fileTypes',
+ type: 'multiOptions',
+ default: [],
+ description: 'Return only items corresponding to the selected MIME types',
+ options: fileTypesOptions.filter((option) => option.name !== 'Folder'),
+ displayOptions: {
+ show: {
+ whatToSearch: ['files'],
+ },
+ },
+ },
+ {
+ displayName: 'Include Trashed Items',
+ name: 'includeTrashed',
+ type: 'boolean',
+ default: false,
+ description: "Whether to return also items in the Drive's bin",
+ },
+ ],
+ },
+ {
+ displayName: 'Options',
+ name: 'options',
+ type: 'collection',
+ placeholder: 'Add Option',
+ default: {},
+ options: [
+ {
+ displayName: 'Fields',
+ name: 'fields',
+ type: 'multiOptions',
+ options: [
+ {
+ name: '*',
+ value: '*',
+ description: 'All fields',
+ },
+ {
+ name: 'explicitlyTrashed',
+ value: 'explicitlyTrashed',
+ },
+ {
+ name: 'exportLinks',
+ value: 'exportLinks',
+ },
+ {
+ name: 'hasThumbnail',
+ value: 'hasThumbnail',
+ },
+ {
+ name: 'iconLink',
+ value: 'iconLink',
+ },
+ {
+ name: 'ID',
+ value: 'id',
+ },
+ {
+ name: 'Kind',
+ value: 'kind',
+ },
+ {
+ name: 'mimeType',
+ value: 'mimeType',
+ },
+ {
+ name: 'Name',
+ value: 'name',
+ },
+ {
+ name: 'Permissions',
+ value: 'permissions',
+ },
+ {
+ name: 'Shared',
+ value: 'shared',
+ },
+ {
+ name: 'Spaces',
+ value: 'spaces',
+ },
+ {
+ name: 'Starred',
+ value: 'starred',
+ },
+ {
+ name: 'thumbnailLink',
+ value: 'thumbnailLink',
+ },
+ {
+ name: 'Trashed',
+ value: 'trashed',
+ },
+ {
+ name: 'Version',
+ value: 'version',
+ },
+ {
+ name: 'webViewLink',
+ value: 'webViewLink',
+ },
+ ],
+ default: [],
+ description: 'The fields to return',
+ },
+ ],
+ },
+];
+
+const displayOptions = {
+ show: {
+ resource: ['fileFolder'],
+ operation: ['search'],
+ },
+};
+
+export const description = updateDisplayOptions(displayOptions, properties);
+
+export async function execute(this: IExecuteFunctions, i: number): Promise {
+ const searchMethod = this.getNodeParameter('searchMethod', i) as string;
+ const options = this.getNodeParameter('options', i, {});
+
+ const query = [];
+
+ const queryString = this.getNodeParameter('queryString', i) as string;
+
+ if (searchMethod === 'name') {
+ query.push(`name contains '${queryString}'`);
+ } else {
+ query.push(queryString);
+ }
+
+ const filter = this.getNodeParameter('filter', i, {}) as SearchFilter;
+
+ let driveId = '';
+ let folderId = '';
+ const returnedTypes: string[] = [];
+
+ if (Object.keys(filter)?.length) {
+ if (filter.folderId) {
+ if (filter.folderId.mode === 'url') {
+ folderId = this.getNodeParameter('filter.folderId', i, undefined, {
+ extractValue: true,
+ }) as string;
+ } else {
+ folderId = filter.folderId.value;
+ }
+ }
+
+ if (folderId && folderId !== RLC_FOLDER_DEFAULT) {
+ query.push(`'${folderId}' in parents`);
+ }
+
+ if (filter.driveId) {
+ let value;
+ if (filter.driveId.mode === 'url') {
+ value = this.getNodeParameter('filter.driveId', i, undefined, {
+ extractValue: true,
+ }) as string;
+ } else {
+ value = filter.driveId.value;
+ }
+ driveId = value;
+ }
+
+ const whatToSearch = filter.whatToSearch || 'all';
+ if (whatToSearch === 'folders') {
+ query.push(`mimeType = '${DRIVE.FOLDER}'`);
+ } else {
+ if (whatToSearch === 'files') {
+ query.push(`mimeType != '${DRIVE.FOLDER}'`);
+ }
+
+ if (filter?.fileTypes?.length && !filter.fileTypes.includes('*')) {
+ filter.fileTypes.forEach((fileType: string) => {
+ returnedTypes.push(`mimeType = '${fileType}'`);
+ });
+ }
+ }
+
+ if (!filter.includeTrashed) {
+ query.push('trashed = false');
+ }
+ }
+
+ if (returnedTypes.length) {
+ query.push(`(${returnedTypes.join(' or ')})`);
+ }
+
+ const queryFields = prepareQueryString(options.fields as string[]);
+
+ const qs: IDataObject = {
+ fields: `nextPageToken, files(${queryFields})`,
+ q: query.filter((q) => q).join(' and '),
+ includeItemsFromAllDrives: true,
+ supportsAllDrives: true,
+ spaces: 'appDataFolder, drive',
+ corpora: 'allDrives',
+ };
+
+ updateDriveScopes(qs, driveId);
+
+ if (!driveId && folderId === RLC_FOLDER_DEFAULT) {
+ qs.corpora = 'user';
+ qs.spaces = 'drive';
+ qs.includeItemsFromAllDrives = false;
+ qs.supportsAllDrives = false;
+ }
+
+ const returnAll = this.getNodeParameter('returnAll', i, false);
+
+ let response;
+ if (returnAll) {
+ response = await googleApiRequestAllItems.call(this, 'GET', 'files', '/drive/v3/files', {}, qs);
+ } else {
+ qs.pageSize = this.getNodeParameter('limit', i);
+ response = await googleApiRequest.call(this, 'GET', '/drive/v3/files', undefined, qs);
+ response = response.files;
+ }
+
+ const executionData = this.helpers.constructExecutionMetaData(
+ this.helpers.returnJsonArray(response as IDataObject[]),
+ { itemData: { item: i } },
+ );
+
+ return executionData;
+}
diff --git a/packages/nodes-base/nodes/Google/Drive/v2/actions/folder/Folder.resource.ts b/packages/nodes-base/nodes/Google/Drive/v2/actions/folder/Folder.resource.ts
new file mode 100644
index 0000000000..370e7188b0
--- /dev/null
+++ b/packages/nodes-base/nodes/Google/Drive/v2/actions/folder/Folder.resource.ts
@@ -0,0 +1,45 @@
+import type { INodeProperties } from 'n8n-workflow';
+
+import * as create from './create.operation';
+import * as deleteFolder from './deleteFolder.operation';
+import * as share from './share.operation';
+
+export { create, deleteFolder, share };
+
+export const description: INodeProperties[] = [
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ noDataExpression: true,
+ displayOptions: {
+ show: {
+ resource: ['folder'],
+ },
+ },
+ options: [
+ {
+ name: 'Create',
+ value: 'create',
+ description: 'Create a folder',
+ action: 'Create folder',
+ },
+ {
+ name: 'Delete',
+ value: 'deleteFolder',
+ description: 'Permanently delete a folder',
+ action: 'Delete folder',
+ },
+ {
+ name: 'Share',
+ value: 'share',
+ description: 'Add sharing permissions to a folder',
+ action: 'Share folder',
+ },
+ ],
+ default: 'create',
+ },
+ ...create.description,
+ ...deleteFolder.description,
+ ...share.description,
+];
diff --git a/packages/nodes-base/nodes/Google/Drive/v2/actions/folder/create.operation.ts b/packages/nodes-base/nodes/Google/Drive/v2/actions/folder/create.operation.ts
new file mode 100644
index 0000000000..8b2165bf32
--- /dev/null
+++ b/packages/nodes-base/nodes/Google/Drive/v2/actions/folder/create.operation.ts
@@ -0,0 +1,109 @@
+import type { IExecuteFunctions } from 'n8n-core';
+import type { IDataObject, INodeExecutionData, INodeProperties } from 'n8n-workflow';
+
+import { updateDisplayOptions } from '../../../../../../utils/utilities';
+import { googleApiRequest } from '../../transport';
+import { driveRLC, folderRLC } from '../common.descriptions';
+import { DRIVE } from '../../helpers/interfaces';
+import { setParentFolder } from '../../helpers/utils';
+
+const properties: INodeProperties[] = [
+ {
+ displayName: 'Folder Name',
+ name: 'name',
+ type: 'string',
+ default: '',
+ placeholder: 'e.g. New Folder',
+ description: "The name of the new folder. If not set, 'Untitled' will be used.",
+ },
+ {
+ ...driveRLC,
+ displayName: 'Parent Drive',
+ description: 'The drive where to create the new folder',
+ },
+ {
+ ...folderRLC,
+ displayName: 'Parent Folder',
+ description: 'The parent folder where to create the new folder',
+ },
+ {
+ displayName: 'Options',
+ name: 'options',
+ type: 'collection',
+ placeholder: 'Add Option',
+ default: {},
+ options: [
+ {
+ displayName: 'Simplify Output',
+ name: 'simplifyOutput',
+ type: 'boolean',
+ default: true,
+ description: 'Whether to return a simplified version of the response instead of all fields',
+ },
+ {
+ displayName: 'Folder Color',
+ name: 'folderColorRgb',
+ type: 'color',
+ default: '',
+ description:
+ 'The color of the folder as an RGB hex string. If an unsupported color is specified, the closest color in the palette will be used instead.',
+ },
+ ],
+ },
+];
+
+const displayOptions = {
+ show: {
+ resource: ['folder'],
+ operation: ['create'],
+ },
+};
+
+export const description = updateDisplayOptions(displayOptions, properties);
+
+export async function execute(this: IExecuteFunctions, i: number): Promise {
+ const name = (this.getNodeParameter('name', i) as string) || 'Untitled';
+
+ const driveId = this.getNodeParameter('driveId', i, undefined, {
+ extractValue: true,
+ }) as string;
+
+ const folderId = this.getNodeParameter('folderId', i, undefined, {
+ extractValue: true,
+ }) as string;
+
+ const body: IDataObject = {
+ name,
+ mimeType: DRIVE.FOLDER,
+ parents: [setParentFolder(folderId, driveId)],
+ };
+
+ const folderColorRgb =
+ (this.getNodeParameter('options.folderColorRgb', i, '') as string) || undefined;
+ if (folderColorRgb) {
+ body.folderColorRgb = folderColorRgb;
+ }
+
+ const simplifyOutput = this.getNodeParameter('options.simplifyOutput', i, true) as boolean;
+ let fields;
+ if (!simplifyOutput) {
+ fields = '*';
+ }
+
+ const qs = {
+ fields,
+ includeItemsFromAllDrives: true,
+ supportsAllDrives: true,
+ spaces: 'appDataFolder, drive',
+ corpora: 'allDrives',
+ };
+
+ const response = await googleApiRequest.call(this, 'POST', '/drive/v3/files', body, qs);
+
+ const executionData = this.helpers.constructExecutionMetaData(
+ this.helpers.returnJsonArray(response as IDataObject[]),
+ { itemData: { item: i } },
+ );
+
+ return executionData;
+}
diff --git a/packages/nodes-base/nodes/Google/Drive/v2/actions/folder/deleteFolder.operation.ts b/packages/nodes-base/nodes/Google/Drive/v2/actions/folder/deleteFolder.operation.ts
new file mode 100644
index 0000000000..d6dc077009
--- /dev/null
+++ b/packages/nodes-base/nodes/Google/Drive/v2/actions/folder/deleteFolder.operation.ts
@@ -0,0 +1,77 @@
+import type { IExecuteFunctions } from 'n8n-core';
+import type { INodeExecutionData, INodeProperties } from 'n8n-workflow';
+
+import { updateDisplayOptions } from '../../../../../../utils/utilities';
+import { googleApiRequest } from '../../transport';
+import { folderNoRootRLC } from '../common.descriptions';
+
+const properties: INodeProperties[] = [
+ {
+ ...folderNoRootRLC,
+ description: 'The folder to delete',
+ },
+ {
+ displayName: 'Options',
+ name: 'options',
+ type: 'collection',
+ placeholder: 'Add Option',
+ default: {},
+ options: [
+ {
+ displayName: 'Delete Permanently',
+ name: 'deletePermanently',
+ type: 'boolean',
+ default: false,
+ description:
+ 'Whether to delete the folder immediately. If false, the folder will be moved to the trash.',
+ },
+ ],
+ },
+];
+
+const displayOptions = {
+ show: {
+ resource: ['folder'],
+ operation: ['deleteFolder'],
+ },
+};
+
+export const description = updateDisplayOptions(displayOptions, properties);
+
+export async function execute(this: IExecuteFunctions, i: number): Promise {
+ const returnData: INodeExecutionData[] = [];
+
+ const folderId = this.getNodeParameter('folderNoRootId', i, undefined, {
+ extractValue: true,
+ }) as string;
+
+ const deletePermanently = this.getNodeParameter('options.deletePermanently', i, false) as boolean;
+
+ const qs = {
+ supportsAllDrives: true,
+ };
+
+ if (deletePermanently) {
+ await googleApiRequest.call(this, 'DELETE', `/drive/v3/files/${folderId}`, undefined, qs);
+ } else {
+ await googleApiRequest.call(
+ this,
+ 'PATCH',
+ `/drive/v3/files/${folderId}`,
+ { trashed: true },
+ qs,
+ );
+ }
+
+ const executionData = this.helpers.constructExecutionMetaData(
+ this.helpers.returnJsonArray({
+ fileId: folderId,
+ success: true,
+ }),
+ { itemData: { item: i } },
+ );
+
+ returnData.push(...executionData);
+
+ return returnData;
+}
diff --git a/packages/nodes-base/nodes/Google/Drive/v2/actions/folder/share.operation.ts b/packages/nodes-base/nodes/Google/Drive/v2/actions/folder/share.operation.ts
new file mode 100644
index 0000000000..e597ec3919
--- /dev/null
+++ b/packages/nodes-base/nodes/Google/Drive/v2/actions/folder/share.operation.ts
@@ -0,0 +1,64 @@
+import type { IExecuteFunctions } from 'n8n-core';
+import type { IDataObject, INodeExecutionData, INodeProperties } from 'n8n-workflow';
+
+import { updateDisplayOptions } from '../../../../../../utils/utilities';
+import { googleApiRequest } from '../../transport';
+import { folderNoRootRLC, permissionsOptions, shareOptions } from '../common.descriptions';
+
+const properties: INodeProperties[] = [
+ {
+ ...folderNoRootRLC,
+ description: 'The folder to share',
+ },
+ permissionsOptions,
+ shareOptions,
+];
+
+const displayOptions = {
+ show: {
+ resource: ['folder'],
+ operation: ['share'],
+ },
+};
+
+export const description = updateDisplayOptions(displayOptions, properties);
+
+export async function execute(this: IExecuteFunctions, i: number): Promise {
+ const returnData: INodeExecutionData[] = [];
+
+ const folderId = this.getNodeParameter('folderNoRootId', i, undefined, {
+ extractValue: true,
+ }) as string;
+
+ const permissions = this.getNodeParameter('permissionsUi', i) as IDataObject;
+
+ const shareOption = this.getNodeParameter('options', i);
+
+ const body: IDataObject = {};
+
+ const qs: IDataObject = {
+ supportsAllDrives: true,
+ };
+
+ if (permissions.permissionsValues) {
+ Object.assign(body, permissions.permissionsValues);
+ }
+
+ Object.assign(qs, shareOption);
+
+ const response = await googleApiRequest.call(
+ this,
+ 'POST',
+ `/drive/v3/files/${folderId}/permissions`,
+ body,
+ qs,
+ );
+
+ const executionData = this.helpers.constructExecutionMetaData(
+ this.helpers.returnJsonArray(response as IDataObject[]),
+ { itemData: { item: i } },
+ );
+ returnData.push(...executionData);
+
+ return returnData;
+}
diff --git a/packages/nodes-base/nodes/Google/Drive/v2/actions/node.type.ts b/packages/nodes-base/nodes/Google/Drive/v2/actions/node.type.ts
new file mode 100644
index 0000000000..61a7b40834
--- /dev/null
+++ b/packages/nodes-base/nodes/Google/Drive/v2/actions/node.type.ts
@@ -0,0 +1,18 @@
+import type { AllEntities } from 'n8n-workflow';
+
+type NodeMap = {
+ drive: 'create' | 'deleteDrive' | 'get' | 'list' | 'update';
+ file:
+ | 'copy'
+ | 'createFromText'
+ | 'download'
+ | 'deleteFile'
+ | 'move'
+ | 'share'
+ | 'upload'
+ | 'update';
+ folder: 'create' | 'deleteFolder' | 'share';
+ fileFolder: 'search';
+};
+
+export type GoogleDriveType = AllEntities;
diff --git a/packages/nodes-base/nodes/Google/Drive/v2/actions/router.ts b/packages/nodes-base/nodes/Google/Drive/v2/actions/router.ts
new file mode 100644
index 0000000000..bb369933bd
--- /dev/null
+++ b/packages/nodes-base/nodes/Google/Drive/v2/actions/router.ts
@@ -0,0 +1,55 @@
+import type { IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
+import { NodeOperationError } from 'n8n-workflow';
+
+import type { GoogleDriveType } from './node.type';
+
+import * as drive from './drive/Drive.resource';
+import * as file from './file/File.resource';
+import * as fileFolder from './fileFolder/FileFolder.resource';
+import * as folder from './folder/Folder.resource';
+
+export async function router(this: IExecuteFunctions): Promise {
+ const items = this.getInputData();
+ const returnData: INodeExecutionData[] = [];
+
+ const resource = this.getNodeParameter('resource', 0);
+ const operation = this.getNodeParameter('operation', 0);
+
+ const googleDrive = {
+ resource,
+ operation,
+ } as GoogleDriveType;
+
+ for (let i = 0; i < items.length; i++) {
+ try {
+ switch (googleDrive.resource) {
+ case 'drive':
+ returnData.push(...(await drive[googleDrive.operation].execute.call(this, i)));
+ break;
+ case 'file':
+ returnData.push(...(await file[googleDrive.operation].execute.call(this, i, items[i])));
+ break;
+ case 'fileFolder':
+ returnData.push(...(await fileFolder[googleDrive.operation].execute.call(this, i)));
+ break;
+ case 'folder':
+ returnData.push(...(await folder[googleDrive.operation].execute.call(this, i)));
+ break;
+ default:
+ throw new NodeOperationError(this.getNode(), `The resource "${resource}" is not known`);
+ }
+ } catch (error) {
+ if (this.continueOnFail()) {
+ if (resource === 'file' && operation === 'download') {
+ items[i].json = { error: error.message };
+ } else {
+ returnData.push({ json: { error: error.message } });
+ }
+ continue;
+ }
+ throw error;
+ }
+ }
+
+ return this.prepareOutputData(returnData);
+}
diff --git a/packages/nodes-base/nodes/Google/Drive/v2/actions/versionDescription.ts b/packages/nodes-base/nodes/Google/Drive/v2/actions/versionDescription.ts
new file mode 100644
index 0000000000..7659ddf551
--- /dev/null
+++ b/packages/nodes-base/nodes/Google/Drive/v2/actions/versionDescription.ts
@@ -0,0 +1,90 @@
+/* eslint-disable n8n-nodes-base/node-filename-against-convention */
+import type { INodeTypeDescription } from 'n8n-workflow';
+
+import * as drive from './drive/Drive.resource';
+import * as file from './file/File.resource';
+import * as fileFolder from './fileFolder/FileFolder.resource';
+import * as folder from './folder/Folder.resource';
+
+export const versionDescription: INodeTypeDescription = {
+ displayName: 'Google Drive',
+ name: 'googleDrive',
+ icon: 'file:googleDrive.svg',
+ group: ['input'],
+ version: 3,
+ subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
+ description: 'Access data on Google Drive',
+ defaults: {
+ name: 'Google Drive',
+ },
+ inputs: ['main'],
+ outputs: ['main'],
+ credentials: [
+ {
+ name: 'googleApi',
+ required: true,
+ displayOptions: {
+ show: {
+ authentication: ['serviceAccount'],
+ },
+ },
+ },
+ {
+ name: 'googleDriveOAuth2Api',
+ required: true,
+ displayOptions: {
+ show: {
+ authentication: ['oAuth2'],
+ },
+ },
+ },
+ ],
+ properties: [
+ {
+ displayName: 'Authentication',
+ name: 'authentication',
+ type: 'options',
+ options: [
+ {
+ // eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
+ name: 'OAuth2 (recommended)',
+ value: 'oAuth2',
+ },
+ {
+ name: 'Service Account',
+ value: 'serviceAccount',
+ },
+ ],
+ default: 'oAuth2',
+ },
+ {
+ displayName: 'Resource',
+ name: 'resource',
+ type: 'options',
+ noDataExpression: true,
+ options: [
+ {
+ name: 'File',
+ value: 'file',
+ },
+ {
+ name: 'File/Folder',
+ value: 'fileFolder',
+ },
+ {
+ name: 'Folder',
+ value: 'folder',
+ },
+ {
+ name: 'Shared Drive',
+ value: 'drive',
+ },
+ ],
+ default: 'file',
+ },
+ ...drive.description,
+ ...file.description,
+ ...fileFolder.description,
+ ...folder.description,
+ ],
+};
diff --git a/packages/nodes-base/nodes/Google/Drive/v2/helpers/interfaces.ts b/packages/nodes-base/nodes/Google/Drive/v2/helpers/interfaces.ts
new file mode 100644
index 0000000000..8da73ac857
--- /dev/null
+++ b/packages/nodes-base/nodes/Google/Drive/v2/helpers/interfaces.ts
@@ -0,0 +1,37 @@
+export const UPLOAD_CHUNK_SIZE = 256 * 1024;
+
+export type SearchFilter = {
+ driveId?: {
+ value: string;
+ mode: string;
+ };
+ folderId?: {
+ value: string;
+ mode: string;
+ };
+ whatToSearch?: 'all' | 'files' | 'folders';
+ fileTypes?: string[];
+ includeTrashed?: boolean;
+};
+
+export const RLC_DRIVE_DEFAULT = 'My Drive';
+export const RLC_FOLDER_DEFAULT = 'root';
+
+export const enum DRIVE {
+ FOLDER = 'application/vnd.google-apps.folder',
+ AUDIO = 'application/vnd.google-apps.audio',
+ DOCUMENT = 'application/vnd.google-apps.document',
+ SDK = 'application/vnd.google-apps.drive-sdk',
+ DRAWING = 'application/vnd.google-apps.drawing',
+ FILE = 'application/vnd.google-apps.file',
+ FORM = 'application/vnd.google-apps.form',
+ FUSIONTABLE = 'application/vnd.google-apps.fusiontable',
+ MAP = 'application/vnd.google-apps.map',
+ PHOTO = 'application/vnd.google-apps.photo',
+ PRESENTATION = 'application/vnd.google-apps.presentation',
+ APP_SCRIPTS = 'application/vnd.google-apps.script',
+ SITES = 'application/vnd.google-apps.sites',
+ SPREADSHEET = 'application/vnd.google-apps.spreadsheet',
+ UNKNOWN = 'application/vnd.google-apps.unknown',
+ VIDEO = 'application/vnd.google-apps.video',
+}
diff --git a/packages/nodes-base/nodes/Google/Drive/v2/helpers/utils.ts b/packages/nodes-base/nodes/Google/Drive/v2/helpers/utils.ts
new file mode 100644
index 0000000000..76df7b01f9
--- /dev/null
+++ b/packages/nodes-base/nodes/Google/Drive/v2/helpers/utils.ts
@@ -0,0 +1,133 @@
+import type { IDataObject, IExecuteFunctions } from 'n8n-workflow';
+import { BINARY_ENCODING, NodeOperationError } from 'n8n-workflow';
+
+import type { Readable } from 'stream';
+import { RLC_DRIVE_DEFAULT, RLC_FOLDER_DEFAULT, UPLOAD_CHUNK_SIZE } from './interfaces';
+
+export function prepareQueryString(fields: string[] | undefined) {
+ let queryFields = 'id, name';
+ if (fields) {
+ if (fields.includes('*')) {
+ queryFields = '*';
+ } else {
+ queryFields = fields.join(', ');
+ }
+ }
+ return queryFields;
+}
+
+export async function getItemBinaryData(
+ this: IExecuteFunctions,
+ inputDataFieldName: string,
+ i: number,
+ chunkSize = UPLOAD_CHUNK_SIZE,
+) {
+ let contentLength: number;
+ let fileContent: Buffer | Readable;
+ let originalFilename: string | undefined;
+ let mimeType;
+
+ if (!inputDataFieldName) {
+ throw new NodeOperationError(
+ this.getNode(),
+ 'The name of the input field containing the binary file data must be set',
+ {
+ itemIndex: i,
+ },
+ );
+ }
+ const binaryData = this.helpers.assertBinaryData(i, inputDataFieldName);
+
+ if (binaryData.id) {
+ // Stream data in 256KB chunks, and upload the via the resumable upload api
+ fileContent = this.helpers.getBinaryStream(binaryData.id, chunkSize);
+ const metadata = await this.helpers.getBinaryMetadata(binaryData.id);
+ contentLength = metadata.fileSize;
+ originalFilename = metadata.fileName;
+ if (metadata.mimeType) mimeType = binaryData.mimeType;
+ } else {
+ fileContent = Buffer.from(binaryData.data, BINARY_ENCODING);
+ contentLength = fileContent.length;
+ originalFilename = binaryData.fileName;
+ mimeType = binaryData.mimeType;
+ }
+
+ return {
+ contentLength,
+ fileContent,
+ originalFilename,
+ mimeType,
+ };
+}
+
+export function setFileProperties(body: IDataObject, options: IDataObject) {
+ if (options.propertiesUi) {
+ const values = ((options.propertiesUi as IDataObject).propertyValues as IDataObject[]) || [];
+
+ body.properties = values.reduce(
+ (acc, value) => Object.assign(acc, { [`${value.key}`]: value.value }),
+ {} as IDataObject,
+ );
+ }
+
+ if (options.appPropertiesUi) {
+ const values =
+ ((options.appPropertiesUi as IDataObject).appPropertyValues as IDataObject[]) || [];
+
+ body.appProperties = values.reduce(
+ (acc, value) => Object.assign(acc, { [`${value.key}`]: value.value }),
+ {} as IDataObject,
+ );
+ }
+
+ return body;
+}
+
+export function setUpdateCommonParams(qs: IDataObject, options: IDataObject) {
+ if (options.keepRevisionForever) {
+ qs.keepRevisionForever = options.keepRevisionForever;
+ }
+
+ if (options.ocrLanguage) {
+ qs.ocrLanguage = options.ocrLanguage;
+ }
+
+ if (options.useContentAsIndexableText) {
+ qs.useContentAsIndexableText = options.useContentAsIndexableText;
+ }
+
+ return qs;
+}
+
+export function updateDriveScopes(
+ qs: IDataObject,
+ driveId: string,
+ defaultDrive = RLC_DRIVE_DEFAULT,
+) {
+ if (driveId) {
+ if (driveId === defaultDrive) {
+ qs.includeItemsFromAllDrives = false;
+ qs.supportsAllDrives = false;
+ qs.spaces = 'appDataFolder, drive';
+ qs.corpora = 'user';
+ } else {
+ qs.driveId = driveId;
+ qs.corpora = 'drive';
+ }
+ }
+}
+
+export function setParentFolder(
+ folderId: string,
+ driveId: string,
+ folderIdDefault = RLC_FOLDER_DEFAULT,
+ driveIdDefault = RLC_DRIVE_DEFAULT,
+) {
+ if (folderId !== folderIdDefault) {
+ return folderId;
+ } else if (driveId && driveId !== driveIdDefault) {
+ return driveId;
+ } else {
+ return 'root';
+ }
+}
diff --git a/packages/nodes-base/nodes/Google/Drive/v2/methods/index.ts b/packages/nodes-base/nodes/Google/Drive/v2/methods/index.ts
new file mode 100644
index 0000000000..c7fb720e47
--- /dev/null
+++ b/packages/nodes-base/nodes/Google/Drive/v2/methods/index.ts
@@ -0,0 +1 @@
+export * as listSearch from './listSearch';
diff --git a/packages/nodes-base/nodes/Google/Drive/v2/methods/listSearch.ts b/packages/nodes-base/nodes/Google/Drive/v2/methods/listSearch.ts
new file mode 100644
index 0000000000..8c634e2699
--- /dev/null
+++ b/packages/nodes-base/nodes/Google/Drive/v2/methods/listSearch.ts
@@ -0,0 +1,199 @@
+import type {
+ IDataObject,
+ ILoadOptionsFunctions,
+ INodeListSearchItems,
+ INodeListSearchResult,
+} from 'n8n-workflow';
+import { googleApiRequest } from '../transport';
+import type { SearchFilter } from '../helpers/interfaces';
+import { DRIVE, RLC_DRIVE_DEFAULT, RLC_FOLDER_DEFAULT } from '../helpers/interfaces';
+import { updateDriveScopes } from '../helpers/utils';
+
+interface FilesItem {
+ id: string;
+ name: string;
+ mimeType: string;
+ webViewLink: string;
+}
+
+interface DriveItem {
+ id: string;
+ name: string;
+}
+
+export async function fileSearch(
+ this: ILoadOptionsFunctions,
+ filter?: string,
+ paginationToken?: string,
+): Promise {
+ const query: string[] = ['trashed = false'];
+ if (filter) {
+ query.push(`name contains '${filter.replace("'", "\\'")}'`);
+ }
+ query.push(`mimeType != '${DRIVE.FOLDER}'`);
+ const res = await googleApiRequest.call(this, 'GET', '/drive/v3/files', undefined, {
+ q: query.join(' and '),
+ pageToken: paginationToken,
+ fields: 'nextPageToken,files(id,name,mimeType,webViewLink)',
+ orderBy: 'name_natural',
+ includeItemsFromAllDrives: true,
+ supportsAllDrives: true,
+ spaces: 'appDataFolder, drive',
+ corpora: 'allDrives',
+ });
+ return {
+ results: res.files.map((file: FilesItem) => ({
+ name: file.name,
+ value: file.id,
+ url: file.webViewLink,
+ })),
+ paginationToken: res.nextPageToken,
+ };
+}
+
+export async function driveSearch(
+ this: ILoadOptionsFunctions,
+ filter?: string,
+ paginationToken?: string,
+): Promise {
+ let res = { drives: [], nextPageToken: undefined };
+
+ res = await googleApiRequest.call(this, 'GET', '/drive/v3/drives', undefined, {
+ q: filter ? `name contains '${filter.replace("'", "\\'")}'` : undefined,
+ pageToken: paginationToken,
+ });
+
+ const results: INodeListSearchItems[] = [];
+
+ res.drives.forEach((drive: DriveItem) => {
+ results.push({
+ name: drive.name,
+ value: drive.id,
+ url: `https://drive.google.com/drive/folders/${drive.id}`,
+ });
+ });
+
+ return {
+ results,
+ paginationToken: res.nextPageToken,
+ };
+}
+
+export async function driveSearchWithDefault(
+ this: ILoadOptionsFunctions,
+ filter?: string,
+ paginationToken?: string,
+): Promise {
+ const drives = await driveSearch.call(this, filter, paginationToken);
+
+ let results: INodeListSearchItems[] = [];
+
+ if (filter && !RLC_DRIVE_DEFAULT.toLowerCase().includes(filter.toLowerCase())) {
+ results = drives.results;
+ } else {
+ results = [
+ {
+ name: RLC_DRIVE_DEFAULT,
+ value: RLC_DRIVE_DEFAULT,
+ url: 'https://drive.google.com/drive/my-drive',
+ },
+ ...drives.results,
+ ];
+ }
+
+ return {
+ results,
+ paginationToken: drives.paginationToken,
+ };
+}
+
+export async function folderSearch(
+ this: ILoadOptionsFunctions,
+ filter?: string,
+ paginationToken?: string,
+): Promise {
+ const query: string[] = [];
+ if (filter) {
+ query.push(`name contains '${filter.replace("'", "\\'")}'`);
+ }
+ query.push(`mimeType = '${DRIVE.FOLDER}'`);
+
+ const qs: IDataObject = {
+ q: query.join(' and '),
+ pageToken: paginationToken,
+ fields: 'nextPageToken,files(id,name,mimeType,webViewLink,parents,driveId)',
+ orderBy: 'name_natural',
+ includeItemsFromAllDrives: true,
+ supportsAllDrives: true,
+ spaces: 'appDataFolder, drive',
+ corpora: 'allDrives',
+ };
+
+ let driveId;
+
+ driveId = this.getNodeParameter('driveId', '') as IDataObject;
+
+ if (!driveId) {
+ const searchFilter = this.getNodeParameter('filter', {}) as SearchFilter;
+ if (searchFilter?.driveId?.mode === 'url') {
+ searchFilter.driveId.value = this.getNodeParameter('filter.folderId', undefined, {
+ extractValue: true,
+ }) as string;
+ }
+ driveId = searchFilter.driveId;
+ }
+ updateDriveScopes(qs, driveId?.value as string);
+
+ const res = await googleApiRequest.call(this, 'GET', '/drive/v3/files', undefined, qs);
+
+ const results: INodeListSearchItems[] = [];
+
+ res.files.forEach((i: FilesItem) => {
+ results.push({
+ name: i.name,
+ value: i.id,
+ url: i.webViewLink,
+ });
+ });
+
+ return {
+ results,
+ paginationToken: res.nextPageToken,
+ };
+}
+
+export async function folderSearchWithDefault(
+ this: ILoadOptionsFunctions,
+ filter?: string,
+ paginationToken?: string,
+): Promise {
+ const folders = await folderSearch.call(this, filter, paginationToken);
+
+ let results: INodeListSearchItems[] = [];
+ const rootDefaultDisplayName = '/ (Root folder)';
+
+ if (
+ filter &&
+ !(
+ RLC_FOLDER_DEFAULT.toLowerCase().includes(filter.toLowerCase()) ||
+ rootDefaultDisplayName.toLowerCase().includes(filter.toLowerCase())
+ )
+ ) {
+ results = folders.results;
+ } else {
+ results = [
+ {
+ // eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
+ name: rootDefaultDisplayName,
+ value: RLC_FOLDER_DEFAULT,
+ url: 'https://drive.google.com/drive',
+ },
+ ...folders.results,
+ ];
+ }
+
+ return {
+ results,
+ paginationToken: folders.paginationToken,
+ };
+}
diff --git a/packages/nodes-base/nodes/Google/Drive/v2/transport/index.ts b/packages/nodes-base/nodes/Google/Drive/v2/transport/index.ts
new file mode 100644
index 0000000000..417ef06c7c
--- /dev/null
+++ b/packages/nodes-base/nodes/Google/Drive/v2/transport/index.ts
@@ -0,0 +1,111 @@
+import type {
+ IExecuteFunctions,
+ IExecuteSingleFunctions,
+ ILoadOptionsFunctions,
+ IDataObject,
+ IPollFunctions,
+ JsonObject,
+ IHttpRequestOptions,
+ IHttpRequestMethods,
+} from 'n8n-workflow';
+import { NodeApiError } from 'n8n-workflow';
+import { getGoogleAccessToken } from '../../../GenericFunctions';
+
+export async function googleApiRequest(
+ this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions | IPollFunctions,
+ method: IHttpRequestMethods,
+ resource: string,
+ body: IDataObject | string | Buffer = {},
+ qs: IDataObject = {},
+ uri?: string,
+ option: IDataObject = {},
+) {
+ const authenticationMethod = this.getNodeParameter(
+ 'authentication',
+ 0,
+ 'serviceAccount',
+ ) as string;
+
+ let options: IHttpRequestOptions = {
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+
+ method,
+ body,
+ qs,
+ url: uri || `https://www.googleapis.com${resource}`,
+ json: true,
+ };
+
+ options = Object.assign({}, options, option);
+
+ try {
+ if (Object.keys(body).length === 0) {
+ delete options.body;
+ }
+
+ if (authenticationMethod === 'serviceAccount') {
+ const credentials = await this.getCredentials('googleApi');
+
+ const { access_token } = await getGoogleAccessToken.call(this, credentials, 'drive');
+
+ options.headers!.Authorization = `Bearer ${access_token}`;
+ return await this.helpers.httpRequest(options);
+ } else {
+ return await this.helpers.requestOAuth2.call(this, 'googleDriveOAuth2Api', options);
+ }
+ } catch (error) {
+ if (error.code === 'ERR_OSSL_PEM_NO_START_LINE') {
+ error.statusCode = '401';
+ }
+
+ const apiError = new NodeApiError(
+ this.getNode(),
+ {
+ reason: error.error,
+ } as JsonObject,
+ { httpCode: String(error.statusCode) },
+ );
+
+ if (
+ apiError.message &&
+ apiError.description &&
+ (apiError.message.toLowerCase().includes('bad request') ||
+ apiError.message.toLowerCase().includes('forbidden') ||
+ apiError.message.toUpperCase().includes('UNKNOWN ERROR'))
+ ) {
+ const message = apiError.message;
+ apiError.message = apiError.description;
+ apiError.description = message;
+ }
+
+ throw apiError;
+ }
+}
+
+export async function googleApiRequestAllItems(
+ this: IExecuteFunctions | ILoadOptionsFunctions | IPollFunctions,
+ method: IHttpRequestMethods,
+ propertyName: string,
+ endpoint: string,
+ body: IDataObject = {},
+ query: IDataObject = {},
+) {
+ const returnData: IDataObject[] = [];
+
+ let responseData;
+ query.maxResults = query.maxResults || 100;
+ query.pageSize = query.pageSize || 100;
+
+ do {
+ responseData = await googleApiRequest.call(this, method, endpoint, body, query);
+ returnData.push.apply(returnData, responseData[propertyName] as IDataObject[]);
+
+ if (responseData.nextPageToken) {
+ query.pageToken = responseData.nextPageToken as string;
+ }
+ } while (responseData.nextPageToken !== undefined && responseData.nextPageToken !== '');
+
+ return returnData;
+}