diff --git a/packages/nodes-base/credentials/HelpScoutOAuth2Api.credentials.ts b/packages/nodes-base/credentials/HelpScoutOAuth2Api.credentials.ts new file mode 100644 index 0000000000..0301bf2c51 --- /dev/null +++ b/packages/nodes-base/credentials/HelpScoutOAuth2Api.credentials.ts @@ -0,0 +1,46 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + +export class HelpScoutOAuth2Api implements ICredentialType { + name = 'helpScoutOAuth2Api'; + extends = [ + 'oAuth2Api', + ]; + displayName = 'HelpScout OAuth2 API'; + properties = [ + { + displayName: 'Authorization URL', + name: 'authUrl', + type: 'hidden' as NodePropertyTypes, + default: 'https://secure.helpscout.net/authentication/authorizeClientApplication', + required: true, + }, + { + displayName: 'Access Token URL', + name: 'accessTokenUrl', + type: 'hidden' as NodePropertyTypes, + default: 'https://api.helpscout.net/v2/oauth2/token', + required: true, + }, + { + displayName: 'Scope', + name: 'scope', + type: 'hidden' as NodePropertyTypes, + default: '', + }, + { + displayName: 'Auth URI Query Parameters', + name: 'authQueryParameters', + type: 'hidden' as NodePropertyTypes, + default: '', + }, + { + displayName: 'Authentication', + name: 'authentication', + type: 'hidden' as NodePropertyTypes, + default: 'body', + }, + ]; +} diff --git a/packages/nodes-base/credentials/MicrosoftExcelOAuth2Api.credentials.ts b/packages/nodes-base/credentials/MicrosoftExcelOAuth2Api.credentials.ts new file mode 100644 index 0000000000..3dc1f4b960 --- /dev/null +++ b/packages/nodes-base/credentials/MicrosoftExcelOAuth2Api.credentials.ts @@ -0,0 +1,21 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + +export class MicrosoftExcelOAuth2Api implements ICredentialType { + name = 'microsoftExcelOAuth2Api'; + extends = [ + 'microsoftOAuth2Api', + ]; + displayName = 'Microsoft OAuth2 API'; + properties = [ + //https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-permissions-and-consent + { + displayName: 'Scope', + name: 'scope', + type: 'hidden' as NodePropertyTypes, + default: 'openid offline_access Files.ReadWrite', + }, + ]; +} diff --git a/packages/nodes-base/credentials/MicrosoftOAuth2Api.credentials.ts b/packages/nodes-base/credentials/MicrosoftOAuth2Api.credentials.ts new file mode 100644 index 0000000000..aa98141e3f --- /dev/null +++ b/packages/nodes-base/credentials/MicrosoftOAuth2Api.credentials.ts @@ -0,0 +1,38 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + +export class MicrosoftOAuth2Api implements ICredentialType { + name = 'microsoftOAuth2Api'; + extends = [ + 'oAuth2Api', + ]; + displayName = 'Microsoft OAuth2 API'; + properties = [ + { + displayName: 'Authorization URL', + name: 'authUrl', + type: 'string' as NodePropertyTypes, + default: 'https://login.microsoftonline.com/{yourtenantid}/oauth2/v2.0/authorize', + }, + { + displayName: 'Access Token URL', + name: 'accessTokenUrl', + type: 'string' as NodePropertyTypes, + default: 'https://login.microsoftonline.com/{yourtenantid}/oauth2/v2.0/token', + }, + { + displayName: 'Auth URI Query Parameters', + name: 'authQueryParameters', + type: 'hidden' as NodePropertyTypes, + default: 'response_mode=query', + }, + { + displayName: 'Authentication', + name: 'authentication', + type: 'hidden' as NodePropertyTypes, + default: 'body', + }, + ]; +} diff --git a/packages/nodes-base/credentials/MicrosoftOneDriveOAuth2Api.credentials.ts b/packages/nodes-base/credentials/MicrosoftOneDriveOAuth2Api.credentials.ts new file mode 100644 index 0000000000..adeeb79670 --- /dev/null +++ b/packages/nodes-base/credentials/MicrosoftOneDriveOAuth2Api.credentials.ts @@ -0,0 +1,21 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + +export class MicrosoftOneDriveOAuth2Api implements ICredentialType { + name = 'microsoftOneDriveOAuth2Api'; + extends = [ + 'microsoftOAuth2Api', + ]; + displayName = 'Microsoft OAuth2 API'; + properties = [ + //https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-permissions-and-consent + { + displayName: 'Scope', + name: 'scope', + type: 'hidden' as NodePropertyTypes, + default: 'openid offline_access Files.ReadWrite.All', + }, + ]; +} diff --git a/packages/nodes-base/credentials/TestOAuth2Api.credentials.ts b/packages/nodes-base/credentials/TestOAuth2Api.credentials.ts new file mode 100644 index 0000000000..2a350faecf --- /dev/null +++ b/packages/nodes-base/credentials/TestOAuth2Api.credentials.ts @@ -0,0 +1,26 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + +const scopes = [ + 'https://www.googleapis.com/auth/calendar', + 'https://www.googleapis.com/auth/calendar.events', +]; + +export class TestOAuth2Api implements ICredentialType { + name = 'testOAuth2Api'; + extends = [ + 'googleOAuth2Api', + ]; + displayName = 'Test OAuth2 API'; + properties = [ + { + displayName: 'Scope', + name: 'scope', + type: 'string' as NodePropertyTypes, + default: '', + placeholder: 'asdf', + }, + ]; +} diff --git a/packages/nodes-base/credentials/ZohoOAuth2Api.credentials.ts b/packages/nodes-base/credentials/ZohoOAuth2Api.credentials.ts index 801b164ffd..12a83493a4 100644 --- a/packages/nodes-base/credentials/ZohoOAuth2Api.credentials.ts +++ b/packages/nodes-base/credentials/ZohoOAuth2Api.credentials.ts @@ -62,7 +62,7 @@ export class ZohoOAuth2Api implements ICredentialType { displayName: 'Scope', name: 'scope', type: 'hidden' as NodePropertyTypes, - default: 'ZohoCRM.modules.ALL', + default: 'ZohoCRM.modules.ALL,ZohoCRM.settings.all,ZohoCRM.users.all', }, { displayName: 'Auth URI Query Parameters', diff --git a/packages/nodes-base/nodes/Google/GoogleDriveTrigger.node.ts b/packages/nodes-base/nodes/Google/GoogleDriveTrigger.node.ts new file mode 100644 index 0000000000..e347222e7e --- /dev/null +++ b/packages/nodes-base/nodes/Google/GoogleDriveTrigger.node.ts @@ -0,0 +1,293 @@ +// import { google } from 'googleapis'; + +// import { +// IHookFunctions, +// IWebhookFunctions, +// } from 'n8n-core'; + +// import { +// IDataObject, +// INodeTypeDescription, +// INodeType, +// IWebhookResponseData, +// } from 'n8n-workflow'; + +// import { getAuthenticationClient } from './GoogleApi'; + + +// export class GoogleDriveTrigger implements INodeType { +// description: INodeTypeDescription = { +// displayName: 'Google Drive Trigger', +// name: 'googleDriveTrigger', +// icon: 'file:googleDrive.png', +// group: ['trigger'], +// version: 1, +// subtitle: '={{$parameter["owner"] + "/" + $parameter["repository"] + ": " + $parameter["events"].join(", ")}}', +// description: 'Starts the workflow when a file on Google Drive got changed.', +// defaults: { +// name: 'Google Drive Trigger', +// color: '#3f87f2', +// }, +// inputs: [], +// outputs: ['main'], +// credentials: [ +// { +// name: 'googleApi', +// required: true, +// } +// ], +// webhooks: [ +// { +// name: 'default', +// httpMethod: 'POST', +// responseMode: 'onReceived', +// path: 'webhook', +// }, +// ], +// properties: [ +// { +// displayName: 'Resource Id', +// name: 'resourceId', +// type: 'string', +// default: '', +// required: true, +// placeholder: '', +// description: 'ID of the resource to watch, for example a file ID.', +// }, +// ], +// }; + +// // @ts-ignore (because of request) +// webhookMethods = { +// default: { +// async checkExists(this: IHookFunctions): Promise { +// // const webhookData = this.getWorkflowStaticData('node'); + +// // if (webhookData.webhookId === undefined) { +// // // No webhook id is set so no webhook can exist +// // return false; +// // } + +// // // Webhook got created before so check if it still exists +// // const owner = this.getNodeParameter('owner') as string; +// // const repository = this.getNodeParameter('repository') as string; +// // const endpoint = `/repos/${owner}/${repository}/hooks/${webhookData.webhookId}`; + +// // try { +// // await githubApiRequest.call(this, 'GET', endpoint, {}); +// // } catch (e) { +// // if (e.message.includes('[404]:')) { +// // // Webhook does not exist +// // delete webhookData.webhookId; +// // delete webhookData.webhookEvents; + +// // return false; +// // } + +// // // Some error occured +// // throw e; +// // } + +// // If it did not error then the webhook exists +// // return true; +// return false; +// }, +// async create(this: IHookFunctions): Promise { +// const webhookUrl = this.getNodeWebhookUrl('default'); + +// const resourceId = this.getNodeParameter('resourceId') as string; + +// const credentials = this.getCredentials('googleApi'); + +// if (credentials === undefined) { +// throw new Error('No credentials got returned!'); +// } + +// const scopes = [ +// 'https://www.googleapis.com/auth/drive', +// 'https://www.googleapis.com/auth/drive.appdata', +// 'https://www.googleapis.com/auth/drive.photos.readonly', +// ]; + +// const client = await getAuthenticationClient(credentials.email as string, credentials.privateKey as string, scopes); + +// const drive = google.drive({ +// version: 'v3', +// auth: client, +// }); + + +// const accessToken = await client.getAccessToken(); +// console.log('accessToken: '); +// console.log(accessToken); + +// const asdf = await drive.changes.getStartPageToken(); +// // console.log('asdf: '); +// // console.log(asdf); + + + + +// const response = await drive.changes.watch({ +// // +// pageToken: asdf.data.startPageToken, +// requestBody: { +// id: 'asdf-test-2', +// address: webhookUrl, +// resourceId, +// type: 'web_hook', +// // page_token: '', +// } +// }); + +// console.log('...response...CREATE'); +// console.log(JSON.stringify(response, null, 2)); + + + + + +// // const endpoint = `/repos/${owner}/${repository}/hooks`; + +// // const body = { +// // name: 'web', +// // config: { +// // url: webhookUrl, +// // content_type: 'json', +// // // secret: '...later...', +// // insecure_ssl: '1', // '0' -> not allow inscure ssl | '1' -> allow insercure SSL +// // }, +// // events, +// // active: true, +// // }; + + +// // let responseData; +// // try { +// // responseData = await githubApiRequest.call(this, 'POST', endpoint, body); +// // } catch (e) { +// // if (e.message.includes('[422]:')) { +// // throw new Error('A webhook with the identical URL exists already. Please delete it manually on Github!'); +// // } + +// // throw e; +// // } + +// // if (responseData.id === undefined || responseData.active !== true) { +// // // Required data is missing so was not successful +// // throw new Error('Github webhook creation response did not contain the expected data.'); +// // } + +// // const webhookData = this.getWorkflowStaticData('node'); +// // webhookData.webhookId = responseData.id as string; +// // webhookData.webhookEvents = responseData.events as string[]; + +// return true; +// }, +// async delete(this: IHookFunctions): Promise { +// const webhookUrl = this.getNodeWebhookUrl('default'); + +// const resourceId = this.getNodeParameter('resourceId') as string; + +// const credentials = this.getCredentials('googleApi'); + +// if (credentials === undefined) { +// throw new Error('No credentials got returned!'); +// } + +// const scopes = [ +// 'https://www.googleapis.com/auth/drive', +// 'https://www.googleapis.com/auth/drive.appdata', +// 'https://www.googleapis.com/auth/drive.photos.readonly', +// ]; + +// const client = await getAuthenticationClient(credentials.email as string, credentials.privateKey as string, scopes); + +// const drive = google.drive({ +// version: 'v3', +// auth: client, +// }); + +// // Remove channel +// const response = await drive.channels.stop({ +// requestBody: { +// id: 'asdf-test-2', +// address: webhookUrl, +// resourceId, +// type: 'web_hook', +// } +// }); + + +// console.log('...response...DELETE'); +// console.log(JSON.stringify(response, null, 2)); + + + +// // const webhookData = this.getWorkflowStaticData('node'); + +// // if (webhookData.webhookId !== undefined) { +// // const owner = this.getNodeParameter('owner') as string; +// // const repository = this.getNodeParameter('repository') as string; +// // const endpoint = `/repos/${owner}/${repository}/hooks/${webhookData.webhookId}`; +// // const body = {}; + +// // try { +// // await githubApiRequest.call(this, 'DELETE', endpoint, body); +// // } catch (e) { +// // return false; +// // } + +// // // Remove from the static workflow data so that it is clear +// // // that no webhooks are registred anymore +// // delete webhookData.webhookId; +// // delete webhookData.webhookEvents; +// // } + +// return true; +// }, +// }, +// }; + + + +// async webhook(this: IWebhookFunctions): Promise { +// const bodyData = this.getBodyData(); + +// console.log(''); +// console.log(''); +// console.log('GOT WEBHOOK CALL'); +// console.log(JSON.stringify(bodyData, null, 2)); + + + +// // Check if the webhook is only the ping from Github to confirm if it workshook_id +// if (bodyData.hook_id !== undefined && bodyData.action === undefined) { +// // Is only the ping and not an actual webhook call. So return 'OK' +// // but do not start the workflow. + +// return { +// webhookResponse: 'OK' +// }; +// } + +// // Is a regular webhoook call + +// // TODO: Add headers & requestPath +// const returnData: IDataObject[] = []; + +// returnData.push( +// { +// body: bodyData, +// headers: this.getHeaderData(), +// query: this.getQueryData(), +// } +// ); + +// return { +// workflowData: [ +// this.helpers.returnJsonArray(returnData) +// ], +// }; +// } +// } diff --git a/packages/nodes-base/nodes/HelpScout/ConversationDescription.ts b/packages/nodes-base/nodes/HelpScout/ConversationDescription.ts new file mode 100644 index 0000000000..1ab9dd88ea --- /dev/null +++ b/packages/nodes-base/nodes/HelpScout/ConversationDescription.ts @@ -0,0 +1,638 @@ +import { INodeProperties } from 'n8n-workflow'; + +export const conversationOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'conversation', + ], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create a new conversation', + }, + { + name: 'Delete', + value: 'delete', + description: 'Delete a conversation', + }, + { + name: 'Get', + value: 'get', + description: 'Get a conversation', + }, + { + name: 'Get All', + value: 'getAll', + description: 'Get all conversations', + }, + ], + default: 'create', + description: 'The operation to perform.', + }, +] as INodeProperties[]; + +export const conversationFields = [ +/* -------------------------------------------------------------------------- */ +/* conversation:create */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Mailbox', + name: 'mailboxId', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getMailboxes', + }, + required: true, + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'conversation', + ], + }, + }, + default: '', + description: 'ID of a mailbox where the conversation is being created', + }, + { + displayName: 'Status', + name: 'status', + type: 'options', + required: true, + options: [ + { + name: 'Active', + value: 'active', + }, + { + name: 'Closed', + value: 'closed', + }, + { + name: 'Pending', + value: 'pending', + }, + ], + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'conversation', + ], + }, + }, + default: '', + description: 'Conversation status', + }, + { + displayName: 'Subject', + name: 'subject', + type: 'string', + required: true, + typeOptions: { + alwaysOpenEditWindow: true, + }, + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'conversation', + ], + }, + }, + default: '', + description: `Conversation’s subject`, + }, + { + displayName: 'Type', + name: 'type', + required: true, + type: 'options', + options: [ + { + name: 'Chat', + value: 'chat', + }, + { + name: 'Email', + value: 'email', + }, + { + name: 'Phone', + value: 'phone', + }, + ], + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'conversation', + ], + }, + }, + default: '', + description: 'Conversation type', + }, + { + displayName: 'Resolve Data', + name: 'resolveData', + type: 'boolean', + default: true, + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'conversation', + ], + }, + }, + description: 'By default the response only contain the ID to resource
. If this option gets activated it
will resolve the data automatically.', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'conversation', + ], + }, + }, + options: [ + { + displayName: 'Assign To', + name: 'assignTo', + type: 'number', + default: 0, + description: 'The Help Scout user assigned to the conversation.', + }, + { + displayName: 'Auto Reply', + name: 'autoReply', + type: 'boolean', + default: false, + description: `When autoReply is set to true, an auto reply will be sent
+ as long as there is at least one customer thread in the conversation.`, + }, + { + displayName: 'Closed At', + name: 'closedAt', + type: 'dateTime', + default: '', + description: `When the conversation was closed, only applicable for imported conversations`, + }, + { + displayName: 'Created At', + name: 'createdAt', + type: 'dateTime', + default: '', + description: `When this conversation was created - ISO 8601 date time`, + }, + { + displayName: 'Customer Email', + name: 'customerEmail', + type: 'string', + default: '', + }, + { + displayName: 'Customer ID', + name: 'customerId', + type: 'number', + default: 0, + }, + { + displayName: 'Imported', + name: 'imported', + type: 'boolean', + default: false, + description: `When imported is set to true, no outgoing emails or notifications will be generated.`, + }, + { + displayName: 'Tags', + name: 'tags', + type: 'multiOptions', + typeOptions: { + loadOptionsMethod: 'getTags', + }, + default: [], + description: 'List of tags to be be added to the conversation', + }, + { + displayName: 'User ID', + name: 'user', + type: 'number', + default: 0, + description: 'ID of the user who is adding the conversation and threads.', + }, + ] + }, + { + displayName: 'Threads', + name: 'threadsUi', + placeholder: 'Add Thread', + type: 'fixedCollection', + typeOptions: { + multipleValues: true, + }, + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'conversation', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Thread', + name: 'threadsValues', + values: [ + { + displayName: 'Type', + name: 'type', + type: 'options', + options: [ + { + name: 'Chat', + value: 'chat' + }, + { + name: 'Customer', + value: 'customer' + }, + { + name: 'Note', + value: 'note' + }, + { + name: 'Phone', + value: 'phone' + }, + { + name: 'Reply', + value: 'reply' + }, + ], + default: '', + }, + { + displayName: 'Text', + name: 'text', + type: 'string', + typeOptions: { + alwaysOpenEditWindow: true + }, + default: '', + description: 'The message text.' + }, + { + displayName: 'Bcc', + name: 'bcc', + displayOptions: { + show: { + type: [ + 'customer' + ], + }, + }, + type: 'string', + typeOptions: { + multipleValues: true, + multipleValueButtonText: 'Add Email', + }, + default: [], + description: 'Email addresses.' + }, + { + displayName: 'Cc', + name: 'cc', + displayOptions: { + show: { + type: [ + 'customer' + ], + }, + }, + type: 'string', + typeOptions: { + multipleValues: true, + multipleValueButtonText: 'Add Email', + }, + default: [], + description: 'Email addresses.' + }, + { + displayName: 'Draft', + name: 'draft', + displayOptions: { + show: { + type: [ + 'reply' + ], + }, + }, + type: 'boolean', + default: false, + description: 'If set to true, a draft reply is created', + }, + ], + }, + ], + }, +/* -------------------------------------------------------------------------- */ +/* conversation:get */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Conversation ID', + name: 'conversationId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + resource: [ + 'conversation', + ], + operation: [ + 'get', + ], + }, + }, + description: 'conversation ID', + }, +/* -------------------------------------------------------------------------- */ +/* conversation:delete */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Conversation ID', + name: 'conversationId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + resource: [ + 'conversation', + ], + operation: [ + 'delete', + ], + }, + }, + description: 'conversation ID', + }, +/* -------------------------------------------------------------------------- */ +/* conversation:getAll */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'conversation', + ], + }, + }, + default: false, + description: 'If all results should be returned or only up to a given limit.', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'conversation', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + }, + default: 50, + description: 'How many results to return.', + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + displayOptions: { + show: { + resource: [ + 'conversation', + ], + operation: [ + 'getAll', + ], + }, + }, + options: [ + { + displayName: 'Assign To', + name: 'assignTo', + type: 'number', + default: 0, + description: 'Filters conversations by assignee id', + }, + { + displayName: 'Embed', + name: 'embed', + type: 'options', + options: [ + { + name: 'Threads', + value: 'threads', + }, + ], + default: '', + description: 'Allows embedding/loading of sub-entities', + }, + { + displayName: 'Folder ID', + name: 'folder', + type: 'string', + default: '', + description: 'Filters conversations from a specific folder id', + }, + { + displayName: 'Mailbox ID', + name: 'mailbox', + type: 'string', + default: '', + description: 'Filters conversations from a specific mailbox', + }, + { + displayName: 'Modified Since', + name: 'modifiedSince', + type: 'dateTime', + default: '', + description: 'Returns only conversations that were modified after this date', + }, + { + displayName: 'Number', + name: 'number', + type: 'number', + default: 0, + typeOptions: { + minValue: 0, + }, + description: 'Looks up conversation by conversation number', + }, + { + displayName: 'Query', + name: 'query', + type: 'string', + typeOptions: { + alwaysOpenEditWindow: true, + }, + default: '', + description: 'Advanced search Examples' + }, + { + displayName: 'Sort Field', + name: 'sortField', + type: 'options', + options: [ + { + name: 'Created At', + value: 'createdAt', + }, + { + name: 'customer Email', + value: 'customerEmail', + }, + { + name: 'customer Name', + value: 'customerName', + }, + { + name: 'Mailbox ID', + value: 'mailboxid', + }, + { + name: 'Modified At', + value: 'modifiedAt', + }, + { + name: 'Number', + value: 'number', + }, + { + name: 'Score', + value: 'score', + }, + { + name: 'Status', + value: 'status', + }, + { + name: 'Subject', + value: 'subject', + }, + ], + default: '', + description: 'Sorts the result by specified field', + }, + { + displayName: 'Sort Order', + name: 'sortOrder', + type: 'options', + options: [ + { + name: 'ASC', + value: 'asc', + }, + { + name: 'DESC', + value: 'desc', + }, + ], + default: 'desc', + }, + { + displayName: 'Status', + name: 'status', + type: 'options', + options: [ + { + name: 'Active', + value: 'active', + }, + { + name: 'All', + value: 'all', + }, + { + name: 'Closed', + value: 'closed', + }, + { + name: 'Open', + value: 'open', + }, + { + name: 'Pending', + value: 'pending', + }, + { + name: 'Spam', + value: 'spam', + }, + ], + default: 'active', + description: 'Filter conversation by status', + }, + { + displayName: 'Tags', + name: 'tags', + type: 'multiOptions', + typeOptions: { + loadOptionsMethod: 'getTags', + }, + default: [], + description: 'Filter conversation by tags', + }, + ], + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/HelpScout/ConversationInterface.ts b/packages/nodes-base/nodes/HelpScout/ConversationInterface.ts new file mode 100644 index 0000000000..ea617f5aa3 --- /dev/null +++ b/packages/nodes-base/nodes/HelpScout/ConversationInterface.ts @@ -0,0 +1,18 @@ +import { IDataObject } from 'n8n-workflow'; + +export interface IConversation { + assignTo?: number; + autoReply?: boolean; + closedAt?: string; + createdAt?: string; + customer?: IDataObject; + fields?: IDataObject[]; + imported?: boolean; + mailboxId?: number; + status?: string; + subject?: string; + tags?: IDataObject[]; + threads?: IDataObject[]; + type?: string; + user?: number; +} diff --git a/packages/nodes-base/nodes/HelpScout/CountriesCodes.ts b/packages/nodes-base/nodes/HelpScout/CountriesCodes.ts new file mode 100644 index 0000000000..3c41a76c7c --- /dev/null +++ b/packages/nodes-base/nodes/HelpScout/CountriesCodes.ts @@ -0,0 +1,1579 @@ +export const countriesCodes = [ + { + 'name': 'Afghanistan', + 'alpha2': 'AF', + 'alpha3': 'AFG', + 'numeric': '004' + }, + { + 'name': 'Åland Islands', + 'alpha2': 'AX', + 'alpha3': 'ALA', + 'numeric': '248', + 'altName': 'Aland Islands' + }, + { + 'name': 'Albania', + 'alpha2': 'AL', + 'alpha3': 'ALB', + 'numeric': '008' + }, + { + 'name': 'Algeria', + 'alpha2': 'DZ', + 'alpha3': 'DZA', + 'numeric': '012' + }, + { + 'name': 'American Samoa', + 'alpha2': 'AS', + 'alpha3': 'ASM', + 'numeric': '016' + }, + { + 'name': 'Andorra', + 'alpha2': 'AD', + 'alpha3': 'AND', + 'numeric': '020' + }, + { + 'name': 'Angola', + 'alpha2': 'AO', + 'alpha3': 'AGO', + 'numeric': '024' + }, + { + 'name': 'Anguilla', + 'alpha2': 'AI', + 'alpha3': 'AIA', + 'numeric': '660' + }, + { + 'name': 'Antarctica', + 'alpha2': 'AQ', + 'alpha3': 'ATA', + 'numeric': '010' + }, + { + 'name': 'Antigua and Barbuda', + 'alpha2': 'AG', + 'alpha3': 'ATG', + 'numeric': '028' + }, + { + 'name': 'Argentina', + 'alpha2': 'AR', + 'alpha3': 'ARG', + 'numeric': '032' + }, + { + 'name': 'Armenia', + 'alpha2': 'AM', + 'alpha3': 'ARM', + 'numeric': '051' + }, + { + 'name': 'Aruba', + 'alpha2': 'AW', + 'alpha3': 'ABW', + 'numeric': '533' + }, + { + 'name': 'Australia', + 'alpha2': 'AU', + 'alpha3': 'AUS', + 'numeric': '036' + }, + { + 'name': 'Austria', + 'alpha2': 'AT', + 'alpha3': 'AUT', + 'numeric': '040' + }, + { + 'name': 'Azerbaijan', + 'alpha2': 'AZ', + 'alpha3': 'AZE', + 'numeric': '031' + }, + { + 'name': 'Bahamas (the)', + 'alpha2': 'BS', + 'alpha3': 'BHS', + 'numeric': '044', + 'altName': 'Bahamas' + }, + { + 'name': 'Bahrain', + 'alpha2': 'BH', + 'alpha3': 'BHR', + 'numeric': '048' + }, + { + 'name': 'Bangladesh', + 'alpha2': 'BD', + 'alpha3': 'BGD', + 'numeric': '050' + }, + { + 'name': 'Barbados', + 'alpha2': 'BB', + 'alpha3': 'BRB', + 'numeric': '052' + }, + { + 'name': 'Belarus', + 'alpha2': 'BY', + 'alpha3': 'BLR', + 'numeric': '112' + }, + { + 'name': 'Belgium', + 'alpha2': 'BE', + 'alpha3': 'BEL', + 'numeric': '056' + }, + { + 'name': 'Belize', + 'alpha2': 'BZ', + 'alpha3': 'BLZ', + 'numeric': '084' + }, + { + 'name': 'Benin', + 'alpha2': 'BJ', + 'alpha3': 'BEN', + 'numeric': '204' + }, + { + 'name': 'Bermuda', + 'alpha2': 'BM', + 'alpha3': 'BMU', + 'numeric': '060' + }, + { + 'name': 'Bhutan', + 'alpha2': 'BT', + 'alpha3': 'BTN', + 'numeric': '064' + }, + { + 'name': 'Bolivia (Plurinational State of)', + 'alpha2': 'BO', + 'alpha3': 'BOL', + 'numeric': '068', + 'altName': 'Bolivia' + }, + { + 'name': 'Bonaire, Sint Eustatius and Saba', + 'alpha2': 'BQ', + 'alpha3': 'BES', + 'numeric': '535' + }, + { + 'name': 'Bosnia and Herzegovina', + 'alpha2': 'BA', + 'alpha3': 'BIH', + 'numeric': '070' + }, + { + 'name': 'Botswana', + 'alpha2': 'BW', + 'alpha3': 'BWA', + 'numeric': '072' + }, + { + 'name': 'Bouvet Island', + 'alpha2': 'BV', + 'alpha3': 'BVT', + 'numeric': '074' + }, + { + 'name': 'Brazil', + 'alpha2': 'BR', + 'alpha3': 'BRA', + 'numeric': '076' + }, + { + 'name': 'British Indian Ocean Territory (the)', + 'alpha2': 'IO', + 'alpha3': 'IOT', + 'numeric': '086', + 'altName': 'British Indian Ocean Territory' + }, + { + 'name': 'Brunei Darussalam', + 'alpha2': 'BN', + 'alpha3': 'BRN', + 'numeric': '096', + 'shortName': 'Brunei' + }, + { + 'name': 'Bulgaria', + 'alpha2': 'BG', + 'alpha3': 'BGR', + 'numeric': '100' + }, + { + 'name': 'Burkina Faso', + 'alpha2': 'BF', + 'alpha3': 'BFA', + 'numeric': '854' + }, + { + 'name': 'Burundi', + 'alpha2': 'BI', + 'alpha3': 'BDI', + 'numeric': '108' + }, + { + 'name': 'Cabo Verde', + 'alpha2': 'CV', + 'alpha3': 'CPV', + 'numeric': '132', + 'altName': 'Cape Verde' + }, + { + 'name': 'Cambodia', + 'alpha2': 'KH', + 'alpha3': 'KHM', + 'numeric': '116' + }, + { + 'name': 'Cameroon', + 'alpha2': 'CM', + 'alpha3': 'CMR', + 'numeric': '120' + }, + { + 'name': 'Canada', + 'alpha2': 'CA', + 'alpha3': 'CAN', + 'numeric': '124' + }, + { + 'name': 'Cayman Islands (the)', + 'alpha2': 'KY', + 'alpha3': 'CYM', + 'numeric': '136', + 'altName': 'Cayman Islands' + }, + { + 'name': 'Central African Republic (the)', + 'alpha2': 'CF', + 'alpha3': 'CAF', + 'numeric': '140', + 'altName': 'Central African Republic' + }, + { + 'name': 'Chad', + 'alpha2': 'TD', + 'alpha3': 'TCD', + 'numeric': '148' + }, + { + 'name': 'Chile', + 'alpha2': 'CL', + 'alpha3': 'CHL', + 'numeric': '152' + }, + { + 'name': 'China', + 'alpha2': 'CN', + 'alpha3': 'CHN', + 'numeric': '156' + }, + { + 'name': 'Christmas Island', + 'alpha2': 'CX', + 'alpha3': 'CXR', + 'numeric': '162' + }, + { + 'name': 'Cocos (Keeling) Islands (the)', + 'alpha2': 'CC', + 'alpha3': 'CCK', + 'numeric': '166', + 'altName': 'Cocos (Keeling) Islands', + 'shortName': 'Cocos Islands' + }, + { + 'name': 'Colombia', + 'alpha2': 'CO', + 'alpha3': 'COL', + 'numeric': '170' + }, + { + 'name': 'Comoros (the)', + 'alpha2': 'KM', + 'alpha3': 'COM', + 'numeric': '174', + 'altName': 'Comoros' + }, + { + 'name': 'Congo (the Democratic Republic of the)', + 'alpha2': 'CD', + 'alpha3': 'COD', + 'numeric': '180', + 'altName': 'Congo, (Kinshasa)', + 'shortName': 'Democratic Republic of the Congo' + }, + { + 'name': 'Congo (the)', + 'alpha2': 'CG', + 'alpha3': 'COG', + 'numeric': '178', + 'altName': 'Congo (Brazzaville)', + 'shortName': 'Republic of the Congo' + }, + { + 'name': 'Cook Islands (the)', + 'alpha2': 'CK', + 'alpha3': 'COK', + 'numeric': '184', + 'altName': 'Cook Islands' + }, + { + 'name': 'Costa Rica', + 'alpha2': 'CR', + 'alpha3': 'CRI', + 'numeric': '188' + }, + { + 'name': 'Côte d\'Ivoire', + 'alpha2': 'CI', + 'alpha3': 'CIV', + 'numeric': '384', + 'shortName': 'Ivory Coast' + }, + { + 'name': 'Croatia', + 'alpha2': 'HR', + 'alpha3': 'HRV', + 'numeric': '191' + }, + { + 'name': 'Cuba', + 'alpha2': 'CU', + 'alpha3': 'CUB', + 'numeric': '192' + }, + { + 'name': 'Curaçao', + 'alpha2': 'CW', + 'alpha3': 'CUW', + 'numeric': '531', + 'shortName': 'Curacao' + }, + { + 'name': 'Cyprus', + 'alpha2': 'CY', + 'alpha3': 'CYP', + 'numeric': '196' + }, + { + 'name': 'Czechia', + 'alpha2': 'CZ', + 'alpha3': 'CZE', + 'numeric': '203', + 'altName': 'Czech Republic' + }, + { + 'name': 'Denmark', + 'alpha2': 'DK', + 'alpha3': 'DNK', + 'numeric': '208' + }, + { + 'name': 'Djibouti', + 'alpha2': 'DJ', + 'alpha3': 'DJI', + 'numeric': '262' + }, + { + 'name': 'Dominica', + 'alpha2': 'DM', + 'alpha3': 'DMA', + 'numeric': '212' + }, + { + 'name': 'Dominican Republic (the)', + 'alpha2': 'DO', + 'alpha3': 'DOM', + 'numeric': '214', + 'altName': 'Dominican Republic' + }, + { + 'name': 'Ecuador', + 'alpha2': 'EC', + 'alpha3': 'ECU', + 'numeric': '218' + }, + { + 'name': 'Egypt', + 'alpha2': 'EG', + 'alpha3': 'EGY', + 'numeric': '818' + }, + { + 'name': 'El Salvador', + 'alpha2': 'SV', + 'alpha3': 'SLV', + 'numeric': '222' + }, + { + 'name': 'Equatorial Guinea', + 'alpha2': 'GQ', + 'alpha3': 'GNQ', + 'numeric': '226' + }, + { + 'name': 'Eritrea', + 'alpha2': 'ER', + 'alpha3': 'ERI', + 'numeric': '232' + }, + { + 'name': 'Estonia', + 'alpha2': 'EE', + 'alpha3': 'EST', + 'numeric': '233' + }, + { + 'name': 'Ethiopia', + 'alpha2': 'ET', + 'alpha3': 'ETH', + 'numeric': '231' + }, + { + 'name': 'Falkland Islands (the) [Malvinas]', + 'alpha2': 'FK', + 'alpha3': 'FLK', + 'numeric': '238', + 'altName': 'Falkland Islands (Malvinas)', + 'shortName': 'Falkland Islands' + }, + { + 'name': 'Faroe Islands (the)', + 'alpha2': 'FO', + 'alpha3': 'FRO', + 'numeric': '234', + 'altName': 'Faroe Islands' + }, + { + 'name': 'Fiji', + 'alpha2': 'FJ', + 'alpha3': 'FJI', + 'numeric': '242' + }, + { + 'name': 'Finland', + 'alpha2': 'FI', + 'alpha3': 'FIN', + 'numeric': '246' + }, + { + 'name': 'France', + 'alpha2': 'FR', + 'alpha3': 'FRA', + 'numeric': '250' + }, + { + 'name': 'French Guiana', + 'alpha2': 'GF', + 'alpha3': 'GUF', + 'numeric': '254' + }, + { + 'name': 'French Polynesia', + 'alpha2': 'PF', + 'alpha3': 'PYF', + 'numeric': '258' + }, + { + 'name': 'French Southern Territories (the)', + 'alpha2': 'TF', + 'alpha3': 'ATF', + 'numeric': '260', + 'altName': 'French Southern Territories' + }, + { + 'name': 'Gabon', + 'alpha2': 'GA', + 'alpha3': 'GAB', + 'numeric': '266' + }, + { + 'name': 'Gambia (the)', + 'alpha2': 'GM', + 'alpha3': 'GMB', + 'numeric': '270', + 'altName': 'Gambia' + }, + { + 'name': 'Georgia', + 'alpha2': 'GE', + 'alpha3': 'GEO', + 'numeric': '268' + }, + { + 'name': 'Germany', + 'alpha2': 'DE', + 'alpha3': 'DEU', + 'numeric': '276' + }, + { + 'name': 'Ghana', + 'alpha2': 'GH', + 'alpha3': 'GHA', + 'numeric': '288' + }, + { + 'name': 'Gibraltar', + 'alpha2': 'GI', + 'alpha3': 'GIB', + 'numeric': '292' + }, + { + 'name': 'Greece', + 'alpha2': 'GR', + 'alpha3': 'GRC', + 'numeric': '300' + }, + { + 'name': 'Greenland', + 'alpha2': 'GL', + 'alpha3': 'GRL', + 'numeric': '304' + }, + { + 'name': 'Grenada', + 'alpha2': 'GD', + 'alpha3': 'GRD', + 'numeric': '308' + }, + { + 'name': 'Guadeloupe', + 'alpha2': 'GP', + 'alpha3': 'GLP', + 'numeric': '312' + }, + { + 'name': 'Guam', + 'alpha2': 'GU', + 'alpha3': 'GUM', + 'numeric': '316' + }, + { + 'name': 'Guatemala', + 'alpha2': 'GT', + 'alpha3': 'GTM', + 'numeric': '320' + }, + { + 'name': 'Guernsey', + 'alpha2': 'GG', + 'alpha3': 'GGY', + 'numeric': '831' + }, + { + 'name': 'Guinea', + 'alpha2': 'GN', + 'alpha3': 'GIN', + 'numeric': '324' + }, + { + 'name': 'Guinea-Bissau', + 'alpha2': 'GW', + 'alpha3': 'GNB', + 'numeric': '624' + }, + { + 'name': 'Guyana', + 'alpha2': 'GY', + 'alpha3': 'GUY', + 'numeric': '328' + }, + { + 'name': 'Haiti', + 'alpha2': 'HT', + 'alpha3': 'HTI', + 'numeric': '332' + }, + { + 'name': 'Heard Island and McDonald Islands', + 'alpha2': 'HM', + 'alpha3': 'HMD', + 'numeric': '334', + 'altName': 'Heard and Mcdonald Islands' + }, + { + 'name': 'Holy See (the)', + 'alpha2': 'VA', + 'alpha3': 'VAT', + 'numeric': '336', + 'altName': 'Holy See (Vatican City State)', + 'shortName': 'Vatican' + }, + { + 'name': 'Honduras', + 'alpha2': 'HN', + 'alpha3': 'HND', + 'numeric': '340' + }, + { + 'name': 'Hong Kong', + 'alpha2': 'HK', + 'alpha3': 'HKG', + 'numeric': '344', + 'altName': 'Hong Kong, SAR China' + }, + { + 'name': 'Hungary', + 'alpha2': 'HU', + 'alpha3': 'HUN', + 'numeric': '348' + }, + { + 'name': 'Iceland', + 'alpha2': 'IS', + 'alpha3': 'ISL', + 'numeric': '352' + }, + { + 'name': 'India', + 'alpha2': 'IN', + 'alpha3': 'IND', + 'numeric': '356' + }, + { + 'name': 'Indonesia', + 'alpha2': 'ID', + 'alpha3': 'IDN', + 'numeric': '360' + }, + { + 'name': 'Iran (Islamic Republic of)', + 'alpha2': 'IR', + 'alpha3': 'IRN', + 'numeric': '364', + 'altName': 'Iran, Islamic Republic of', + 'shortName': 'Iran' + }, + { + 'name': 'Iraq', + 'alpha2': 'IQ', + 'alpha3': 'IRQ', + 'numeric': '368' + }, + { + 'name': 'Ireland', + 'alpha2': 'IE', + 'alpha3': 'IRL', + 'numeric': '372' + }, + { + 'name': 'Isle of Man', + 'alpha2': 'IM', + 'alpha3': 'IMN', + 'numeric': '833' + }, + { + 'name': 'Israel', + 'alpha2': 'IL', + 'alpha3': 'ISR', + 'numeric': '376' + }, + { + 'name': 'Italy', + 'alpha2': 'IT', + 'alpha3': 'ITA', + 'numeric': '380' + }, + { + 'name': 'Jamaica', + 'alpha2': 'JM', + 'alpha3': 'JAM', + 'numeric': '388' + }, + { + 'name': 'Japan', + 'alpha2': 'JP', + 'alpha3': 'JPN', + 'numeric': '392' + }, + { + 'name': 'Jersey', + 'alpha2': 'JE', + 'alpha3': 'JEY', + 'numeric': '832' + }, + { + 'name': 'Jordan', + 'alpha2': 'JO', + 'alpha3': 'JOR', + 'numeric': '400' + }, + { + 'name': 'Kazakhstan', + 'alpha2': 'KZ', + 'alpha3': 'KAZ', + 'numeric': '398' + }, + { + 'name': 'Kenya', + 'alpha2': 'KE', + 'alpha3': 'KEN', + 'numeric': '404' + }, + { + 'name': 'Kiribati', + 'alpha2': 'KI', + 'alpha3': 'KIR', + 'numeric': '296' + }, + { + 'name': 'Korea (the Democratic People\'s Republic of)', + 'alpha2': 'KP', + 'alpha3': 'PRK', + 'numeric': '408', + 'altName': 'Korea (North)', + 'shortName': 'North Korea' + }, + { + 'name': 'Korea (the Republic of)', + 'alpha2': 'KR', + 'alpha3': 'KOR', + 'numeric': '410', + 'altName': 'Korea (South)', + 'shortName': 'South Korea' + }, + { + 'name': 'Kuwait', + 'alpha2': 'KW', + 'alpha3': 'KWT', + 'numeric': '414' + }, + { + 'name': 'Kyrgyzstan', + 'alpha2': 'KG', + 'alpha3': 'KGZ', + 'numeric': '417' + }, + { + 'name': 'Lao People\'s Democratic Republic (the)', + 'alpha2': 'LA', + 'alpha3': 'LAO', + 'numeric': '418', + 'altName': 'Lao PDR', + 'shortName': 'Laos' + }, + { + 'name': 'Latvia', + 'alpha2': 'LV', + 'alpha3': 'LVA', + 'numeric': '428' + }, + { + 'name': 'Lebanon', + 'alpha2': 'LB', + 'alpha3': 'LBN', + 'numeric': '422' + }, + { + 'name': 'Lesotho', + 'alpha2': 'LS', + 'alpha3': 'LSO', + 'numeric': '426' + }, + { + 'name': 'Liberia', + 'alpha2': 'LR', + 'alpha3': 'LBR', + 'numeric': '430' + }, + { + 'name': 'Libya', + 'alpha2': 'LY', + 'alpha3': 'LBY', + 'numeric': '434' + }, + { + 'name': 'Liechtenstein', + 'alpha2': 'LI', + 'alpha3': 'LIE', + 'numeric': '438' + }, + { + 'name': 'Lithuania', + 'alpha2': 'LT', + 'alpha3': 'LTU', + 'numeric': '440' + }, + { + 'name': 'Luxembourg', + 'alpha2': 'LU', + 'alpha3': 'LUX', + 'numeric': '442' + }, + { + 'name': 'Macao', + 'alpha2': 'MO', + 'alpha3': 'MAC', + 'numeric': '446', + 'altName': 'Macao, SAR China', + 'shortName': 'Macau' + }, + { + 'name': 'Macedonia (the former Yugoslav Republic of)', + 'alpha2': 'MK', + 'alpha3': 'MKD', + 'numeric': '807', + 'altName': 'Macedonia, Republic of', + 'shortName': 'Macedonia' + }, + { + 'name': 'Madagascar', + 'alpha2': 'MG', + 'alpha3': 'MDG', + 'numeric': '450' + }, + { + 'name': 'Malawi', + 'alpha2': 'MW', + 'alpha3': 'MWI', + 'numeric': '454' + }, + { + 'name': 'Malaysia', + 'alpha2': 'MY', + 'alpha3': 'MYS', + 'numeric': '458' + }, + { + 'name': 'Maldives', + 'alpha2': 'MV', + 'alpha3': 'MDV', + 'numeric': '462' + }, + { + 'name': 'Mali', + 'alpha2': 'ML', + 'alpha3': 'MLI', + 'numeric': '466' + }, + { + 'name': 'Malta', + 'alpha2': 'MT', + 'alpha3': 'MLT', + 'numeric': '470' + }, + { + 'name': 'Marshall Islands (the)', + 'alpha2': 'MH', + 'alpha3': 'MHL', + 'numeric': '584', + 'altName': 'Marshall Islands' + }, + { + 'name': 'Martinique', + 'alpha2': 'MQ', + 'alpha3': 'MTQ', + 'numeric': '474' + }, + { + 'name': 'Mauritania', + 'alpha2': 'MR', + 'alpha3': 'MRT', + 'numeric': '478' + }, + { + 'name': 'Mauritius', + 'alpha2': 'MU', + 'alpha3': 'MUS', + 'numeric': '480' + }, + { + 'name': 'Mayotte', + 'alpha2': 'YT', + 'alpha3': 'MYT', + 'numeric': '175' + }, + { + 'name': 'Mexico', + 'alpha2': 'MX', + 'alpha3': 'MEX', + 'numeric': '484' + }, + { + 'name': 'Micronesia (Federated States of)', + 'alpha2': 'FM', + 'alpha3': 'FSM', + 'numeric': '583', + 'altName': 'Micronesia, Federated States of', + 'shortName': 'Micronesia' + }, + { + 'name': 'Moldova (the Republic of)', + 'alpha2': 'MD', + 'alpha3': 'MDA', + 'numeric': '498', + 'altName': 'Moldova' + }, + { + 'name': 'Monaco', + 'alpha2': 'MC', + 'alpha3': 'MCO', + 'numeric': '492' + }, + { + 'name': 'Mongolia', + 'alpha2': 'MN', + 'alpha3': 'MNG', + 'numeric': '496' + }, + { + 'name': 'Montenegro', + 'alpha2': 'ME', + 'alpha3': 'MNE', + 'numeric': '499' + }, + { + 'name': 'Montserrat', + 'alpha2': 'MS', + 'alpha3': 'MSR', + 'numeric': '500' + }, + { + 'name': 'Morocco', + 'alpha2': 'MA', + 'alpha3': 'MAR', + 'numeric': '504' + }, + { + 'name': 'Mozambique', + 'alpha2': 'MZ', + 'alpha3': 'MOZ', + 'numeric': '508' + }, + { + 'name': 'Myanmar', + 'alpha2': 'MM', + 'alpha3': 'MMR', + 'numeric': '104' + }, + { + 'name': 'Namibia', + 'alpha2': 'NA', + 'alpha3': 'NAM', + 'numeric': '516' + }, + { + 'name': 'Nauru', + 'alpha2': 'NR', + 'alpha3': 'NRU', + 'numeric': '520' + }, + { + 'name': 'Nepal', + 'alpha2': 'NP', + 'alpha3': 'NPL', + 'numeric': '524' + }, + { + 'name': 'Netherlands (the)', + 'alpha2': 'NL', + 'alpha3': 'NLD', + 'numeric': '528', + 'altName': 'Netherlands' + }, + { + 'name': 'New Caledonia', + 'alpha2': 'NC', + 'alpha3': 'NCL', + 'numeric': '540' + }, + { + 'name': 'New Zealand', + 'alpha2': 'NZ', + 'alpha3': 'NZL', + 'numeric': '554' + }, + { + 'name': 'Nicaragua', + 'alpha2': 'NI', + 'alpha3': 'NIC', + 'numeric': '558' + }, + { + 'name': 'Niger (the)', + 'alpha2': 'NE', + 'alpha3': 'NER', + 'numeric': '562', + 'altName': 'Niger' + }, + { + 'name': 'Nigeria', + 'alpha2': 'NG', + 'alpha3': 'NGA', + 'numeric': '566' + }, + { + 'name': 'Niue', + 'alpha2': 'NU', + 'alpha3': 'NIU', + 'numeric': '570' + }, + { + 'name': 'Norfolk Island', + 'alpha2': 'NF', + 'alpha3': 'NFK', + 'numeric': '574' + }, + { + 'name': 'Northern Mariana Islands (the)', + 'alpha2': 'MP', + 'alpha3': 'MNP', + 'numeric': '580', + 'altName': 'Northern Mariana Islands' + }, + { + 'name': 'Norway', + 'alpha2': 'NO', + 'alpha3': 'NOR', + 'numeric': '578' + }, + { + 'name': 'Oman', + 'alpha2': 'OM', + 'alpha3': 'OMN', + 'numeric': '512' + }, + { + 'name': 'Pakistan', + 'alpha2': 'PK', + 'alpha3': 'PAK', + 'numeric': '586' + }, + { + 'name': 'Palau', + 'alpha2': 'PW', + 'alpha3': 'PLW', + 'numeric': '585' + }, + { + 'name': 'Palestine, State of', + 'alpha2': 'PS', + 'alpha3': 'PSE', + 'numeric': '275', + 'altName': 'Palestinian Territory', + 'shortName': 'Palestine' + }, + { + 'name': 'Panama', + 'alpha2': 'PA', + 'alpha3': 'PAN', + 'numeric': '591' + }, + { + 'name': 'Papua New Guinea', + 'alpha2': 'PG', + 'alpha3': 'PNG', + 'numeric': '598' + }, + { + 'name': 'Paraguay', + 'alpha2': 'PY', + 'alpha3': 'PRY', + 'numeric': '600' + }, + { + 'name': 'Peru', + 'alpha2': 'PE', + 'alpha3': 'PER', + 'numeric': '604' + }, + { + 'name': 'Philippines (the)', + 'alpha2': 'PH', + 'alpha3': 'PHL', + 'numeric': '608', + 'altName': 'Philippines' + }, + { + 'name': 'Pitcairn', + 'alpha2': 'PN', + 'alpha3': 'PCN', + 'numeric': '612' + }, + { + 'name': 'Poland', + 'alpha2': 'PL', + 'alpha3': 'POL', + 'numeric': '616' + }, + { + 'name': 'Portugal', + 'alpha2': 'PT', + 'alpha3': 'PRT', + 'numeric': '620' + }, + { + 'name': 'Puerto Rico', + 'alpha2': 'PR', + 'alpha3': 'PRI', + 'numeric': '630' + }, + { + 'name': 'Qatar', + 'alpha2': 'QA', + 'alpha3': 'QAT', + 'numeric': '634' + }, + { + 'name': 'Réunion', + 'alpha2': 'RE', + 'alpha3': 'REU', + 'numeric': '638', + 'shortName': 'Reunion' + }, + { + 'name': 'Romania', + 'alpha2': 'RO', + 'alpha3': 'ROU', + 'numeric': '642' + }, + { + 'name': 'Russian Federation (the)', + 'alpha2': 'RU', + 'alpha3': 'RUS', + 'numeric': '643', + 'altName': 'Russian Federation', + 'shortName': 'Russia' + }, + { + 'name': 'Rwanda', + 'alpha2': 'RW', + 'alpha3': 'RWA', + 'numeric': '646' + }, + { + 'name': 'Saint Barthélemy', + 'alpha2': 'BL', + 'alpha3': 'BLM', + 'numeric': '652', + 'altName': 'Saint-Barthélemy', + 'shortName': 'Saint Barthelemy' + }, + { + 'name': 'Saint Helena, Ascension and Tristan da Cunha', + 'alpha2': 'SH', + 'alpha3': 'SHN', + 'numeric': '654', + 'altName': 'Saint Helena' + }, + { + 'name': 'Saint Kitts and Nevis', + 'alpha2': 'KN', + 'alpha3': 'KNA', + 'numeric': '659' + }, + { + 'name': 'Saint Lucia', + 'alpha2': 'LC', + 'alpha3': 'LCA', + 'numeric': '662' + }, + { + 'name': 'Saint Martin (French part)', + 'alpha2': 'MF', + 'alpha3': 'MAF', + 'numeric': '663', + 'altName': 'Saint-Martin (French part)', + 'shortName': 'Saint Martin' + }, + { + 'name': 'Saint Pierre and Miquelon', + 'alpha2': 'PM', + 'alpha3': 'SPM', + 'numeric': '666' + }, + { + 'name': 'Saint Vincent and the Grenadines', + 'alpha2': 'VC', + 'alpha3': 'VCT', + 'numeric': '670', + 'altName': 'Saint Vincent and Grenadines' + }, + { + 'name': 'Samoa', + 'alpha2': 'WS', + 'alpha3': 'WSM', + 'numeric': '882' + }, + { + 'name': 'San Marino', + 'alpha2': 'SM', + 'alpha3': 'SMR', + 'numeric': '674' + }, + { + 'name': 'Sao Tome and Principe', + 'alpha2': 'ST', + 'alpha3': 'STP', + 'numeric': '678' + }, + { + 'name': 'Saudi Arabia', + 'alpha2': 'SA', + 'alpha3': 'SAU', + 'numeric': '682' + }, + { + 'name': 'Senegal', + 'alpha2': 'SN', + 'alpha3': 'SEN', + 'numeric': '686' + }, + { + 'name': 'Serbia', + 'alpha2': 'RS', + 'alpha3': 'SRB', + 'numeric': '688' + }, + { + 'name': 'Seychelles', + 'alpha2': 'SC', + 'alpha3': 'SYC', + 'numeric': '690' + }, + { + 'name': 'Sierra Leone', + 'alpha2': 'SL', + 'alpha3': 'SLE', + 'numeric': '694' + }, + { + 'name': 'Singapore', + 'alpha2': 'SG', + 'alpha3': 'SGP', + 'numeric': '702' + }, + { + 'name': 'Sint Maarten (Dutch part)', + 'alpha2': 'SX', + 'alpha3': 'SXM', + 'numeric': '534', + 'shortName': 'Sint Maarten' + }, + { + 'name': 'Slovakia', + 'alpha2': 'SK', + 'alpha3': 'SVK', + 'numeric': '703' + }, + { + 'name': 'Slovenia', + 'alpha2': 'SI', + 'alpha3': 'SVN', + 'numeric': '705' + }, + { + 'name': 'Solomon Islands', + 'alpha2': 'SB', + 'alpha3': 'SLB', + 'numeric': '090' + }, + { + 'name': 'Somalia', + 'alpha2': 'SO', + 'alpha3': 'SOM', + 'numeric': '706' + }, + { + 'name': 'South Africa', + 'alpha2': 'ZA', + 'alpha3': 'ZAF', + 'numeric': '710' + }, + { + 'name': 'South Georgia and the South Sandwich Islands', + 'alpha2': 'GS', + 'alpha3': 'SGS', + 'numeric': '239' + }, + { + 'name': 'South Sudan', + 'alpha2': 'SS', + 'alpha3': 'SSD', + 'numeric': '728' + }, + { + 'name': 'Spain', + 'alpha2': 'ES', + 'alpha3': 'ESP', + 'numeric': '724' + }, + { + 'name': 'Sri Lanka', + 'alpha2': 'LK', + 'alpha3': 'LKA', + 'numeric': '144' + }, + { + 'name': 'Sudan (the)', + 'alpha2': 'SD', + 'alpha3': 'SDN', + 'numeric': '729', + 'altName': 'Sudan' + }, + { + 'name': 'Suriname', + 'alpha2': 'SR', + 'alpha3': 'SUR', + 'numeric': '740' + }, + { + 'name': 'Svalbard and Jan Mayen', + 'alpha2': 'SJ', + 'alpha3': 'SJM', + 'numeric': '744', + 'altName': 'Svalbard and Jan Mayen Islands' + }, + { + 'name': 'Swaziland', + 'alpha2': 'SZ', + 'alpha3': 'SWZ', + 'numeric': '748' + }, + { + 'name': 'Sweden', + 'alpha2': 'SE', + 'alpha3': 'SWE', + 'numeric': '752' + }, + { + 'name': 'Switzerland', + 'alpha2': 'CH', + 'alpha3': 'CHE', + 'numeric': '756' + }, + { + 'name': 'Syrian Arab Republic', + 'alpha2': 'SY', + 'alpha3': 'SYR', + 'numeric': '760', + 'altName': 'Syrian Arab Republic (Syria)', + 'shortName': 'Syria' + }, + { + 'name': 'Taiwan (Province of China)', + 'alpha2': 'TW', + 'alpha3': 'TWN', + 'numeric': '158', + 'altName': 'Taiwan, Republic of China', + 'shortName': 'Taiwan' + }, + { + 'name': 'Tajikistan', + 'alpha2': 'TJ', + 'alpha3': 'TJK', + 'numeric': '762' + }, + { + 'name': 'Tanzania, United Republic of', + 'alpha2': 'TZ', + 'alpha3': 'TZA', + 'numeric': '834', + 'shortName': 'Tanzania' + }, + { + 'name': 'Thailand', + 'alpha2': 'TH', + 'alpha3': 'THA', + 'numeric': '764' + }, + { + 'name': 'Timor-Leste', + 'alpha2': 'TL', + 'alpha3': 'TLS', + 'numeric': '626', + 'shortName': 'East Timor' + }, + { + 'name': 'Togo', + 'alpha2': 'TG', + 'alpha3': 'TGO', + 'numeric': '768' + }, + { + 'name': 'Tokelau', + 'alpha2': 'TK', + 'alpha3': 'TKL', + 'numeric': '772' + }, + { + 'name': 'Tonga', + 'alpha2': 'TO', + 'alpha3': 'TON', + 'numeric': '776' + }, + { + 'name': 'Trinidad and Tobago', + 'alpha2': 'TT', + 'alpha3': 'TTO', + 'numeric': '780' + }, + { + 'name': 'Tunisia', + 'alpha2': 'TN', + 'alpha3': 'TUN', + 'numeric': '788' + }, + { + 'name': 'Turkey', + 'alpha2': 'TR', + 'alpha3': 'TUR', + 'numeric': '792' + }, + { + 'name': 'Turkmenistan', + 'alpha2': 'TM', + 'alpha3': 'TKM', + 'numeric': '795' + }, + { + 'name': 'Turks and Caicos Islands (the)', + 'alpha2': 'TC', + 'alpha3': 'TCA', + 'numeric': '796', + 'altName': 'Turks and Caicos Islands' + }, + { + 'name': 'Tuvalu', + 'alpha2': 'TV', + 'alpha3': 'TUV', + 'numeric': '798' + }, + { + 'name': 'Uganda', + 'alpha2': 'UG', + 'alpha3': 'UGA', + 'numeric': '800' + }, + { + 'name': 'Ukraine', + 'alpha2': 'UA', + 'alpha3': 'UKR', + 'numeric': '804' + }, + { + 'name': 'United Arab Emirates (the)', + 'alpha2': 'AE', + 'alpha3': 'ARE', + 'numeric': '784', + 'altName': 'United Arab Emirates' + }, + { + 'name': 'United Kingdom of Great Britain and Northern Ireland (the)', + 'alpha2': 'GB', + 'alpha3': 'GBR', + 'numeric': '826', + 'altName': 'United Kingdom' + }, + { + 'name': 'United States Minor Outlying Islands (the)', + 'alpha2': 'UM', + 'alpha3': 'UMI', + 'numeric': '581', + 'altName': 'US Minor Outlying Islands' + }, + { + 'name': 'United States of America (the)', + 'alpha2': 'US', + 'alpha3': 'USA', + 'numeric': '840', + 'altName': 'United States of America', + 'shortName': 'United States' + }, + { + 'name': 'Uruguay', + 'alpha2': 'UY', + 'alpha3': 'URY', + 'numeric': '858' + }, + { + 'name': 'Uzbekistan', + 'alpha2': 'UZ', + 'alpha3': 'UZB', + 'numeric': '860' + }, + { + 'name': 'Vanuatu', + 'alpha2': 'VU', + 'alpha3': 'VUT', + 'numeric': '548' + }, + { + 'name': 'Venezuela (Bolivarian Republic of)', + 'alpha2': 'VE', + 'alpha3': 'VEN', + 'numeric': '862', + 'altName': 'Venezuela (Bolivarian Republic)', + 'shortName': 'Venezuela' + }, + { + 'name': 'Viet Nam', + 'alpha2': 'VN', + 'alpha3': 'VNM', + 'numeric': '704', + 'shortName': 'Vietnam' + }, + { + 'name': 'Virgin Islands (British)', + 'alpha2': 'VG', + 'alpha3': 'VGB', + 'numeric': '092', + 'altName': 'British Virgin Islands' + }, + { + 'name': 'Virgin Islands (U.S.)', + 'alpha2': 'VI', + 'alpha3': 'VIR', + 'numeric': '850', + 'altName': 'Virgin Islands, US', + 'shortName': 'U.S. Virgin Islands' + }, + { + 'name': 'Wallis and Futuna', + 'alpha2': 'WF', + 'alpha3': 'WLF', + 'numeric': '876', + 'altName': 'Wallis and Futuna Islands' + }, + { + 'name': 'Western Sahara*', + 'alpha2': 'EH', + 'alpha3': 'ESH', + 'numeric': '732', + 'altName': 'Western Sahara' + }, + { + 'name': 'Yemen', + 'alpha2': 'YE', + 'alpha3': 'YEM', + 'numeric': '887' + }, + { + 'name': 'Zambia', + 'alpha2': 'ZM', + 'alpha3': 'ZMB', + 'numeric': '894' + }, + { + 'name': 'Zimbabwe', + 'alpha2': 'ZW', + 'alpha3': 'ZWE', + 'numeric': '716' + } +]; diff --git a/packages/nodes-base/nodes/HelpScout/CustomerDescription.ts b/packages/nodes-base/nodes/HelpScout/CustomerDescription.ts new file mode 100644 index 0000000000..1b3466d078 --- /dev/null +++ b/packages/nodes-base/nodes/HelpScout/CustomerDescription.ts @@ -0,0 +1,851 @@ +import { INodeProperties } from 'n8n-workflow'; + +export const customerOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'customer', + ], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create a new customer', + }, + { + name: 'Get', + value: 'get', + description: 'Get a customer', + }, + { + name: 'Get All', + value: 'getAll', + description: 'Get all customers', + }, + { + name: 'Properties', + value: 'properties', + description: 'Get customer property definitions', + }, + { + name: 'Update', + value: 'update', + description: 'Update a customer', + }, + ], + default: 'create', + description: 'The operation to perform.', + }, +] as INodeProperties[]; + +export const customerFields = [ +/* -------------------------------------------------------------------------- */ +/* customer:create */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Resolve Data', + name: 'resolveData', + type: 'boolean', + default: true, + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'customer', + ], + }, + }, + description: 'By default the response only contain the ID to resource
. If this option gets activated it
will resolve the data automatically.', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'customer', + ], + }, + }, + options: [ + { + displayName: 'Age', + name: 'age', + type: 'number', + typeOptions: { + minValue: 1, + }, + default: 1, + description: `Customer’s age`, + }, + { + displayName: 'First Name', + name: 'firstName', + type: 'string', + default: '', + description: `First name of the customer. When defined it must be between 1 and 40 characters.`, + }, + { + displayName: 'Gender', + name: 'gender', + type: 'options', + options: [ + { + name: 'Female', + value: 'female', + }, + { + name: 'Male', + value: 'male', + }, + { + name: 'Unknown', + value: 'unknown', + }, + ], + default: '', + description: 'Gender of this customer.', + }, + { + displayName: 'Job Title', + name: 'jobTitle', + type: 'string', + default: '', + description: 'Job title. Max length 60 characters.', + }, + { + displayName: 'Last Name', + name: 'lastName', + type: 'string', + default: '', + description: 'Last name of the customer', + }, + { + displayName: 'Location', + name: 'location', + type: 'string', + default: '', + description: 'Location of the customer.', + }, + { + displayName: 'Notes', + name: 'background', + type: 'string', + typeOptions: { + alwaysOpenEditWindow: true, + }, + default: '', + description: `Notes`, + }, + { + displayName: 'Organization', + name: 'organization', + type: 'string', + default: '', + description: 'Organization', + }, + { + displayName: 'Photo Url', + name: 'photoUrl', + type: 'string', + default: '', + description: 'URL of the customer’s photo', + }, + ] + }, + { + displayName: 'Address', + name: 'addressUi', + placeholder: 'Add Address', + type: 'fixedCollection', + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'customer', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Address', + name: 'addressValue', + values: [ + { + displayName: 'Line 1', + name: 'line1', + type: 'string', + default: '', + description: 'line1', + }, + { + displayName: 'Line 2', + name: 'line2', + type: 'string', + default: '', + description: 'line2', + }, + { + displayName: 'City', + name: 'city', + type: 'string', + default: '', + description: 'City', + }, + { + displayName: 'State', + name: 'state', + type: 'string', + default: '', + description: 'State', + }, + { + displayName: 'Country', + name: 'country', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getCountriesCodes', + }, + default: '', + description: 'Country', + }, + { + displayName: 'Postal Code', + name: 'postalCode', + type: 'string', + default: '', + description: 'Postal code', + }, + ], + }, + ], + }, + { + displayName: 'Chat Handles', + name: 'chatsUi', + placeholder: 'Add Chat Handle', + type: 'fixedCollection', + typeOptions: { + multipleValues: true, + }, + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'customer', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Chat Handle', + name: 'chatsValues', + values: [ + { + displayName: 'Type', + name: 'type', + type: 'options', + options: [ + { + name: 'AIM', + value: 'aim', + }, + { + name: 'Google Talk', + value: 'gtalk', + }, + { + name: 'ICQ', + value: 'icq', + }, + { + name: 'MSN', + value: 'msn', + }, + { + name: 'Other', + value: 'other', + }, + { + name: 'QQ', + value: 'qq', + }, + { + name: 'Skype', + value: 'skype', + }, + { + name: 'XMPP', + value: 'xmpp', + }, + { + name: 'Yahoo', + value: 'yahoo', + }, + ], + description: 'Chat type', + default: '', + }, + { + displayName: 'Value', + name: 'value', + type: 'string', + default: '', + description: 'Chat handle', + }, + ], + }, + ], + }, + { + displayName: 'Emails', + name: 'emailsUi', + placeholder: 'Add Email', + type: 'fixedCollection', + typeOptions: { + multipleValues: true, + }, + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'customer', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Email', + name: 'emailsValues', + values: [ + { + displayName: 'Type', + name: 'type', + type: 'options', + options: [ + { + name: 'Home', + value: 'home', + }, + { + name: 'Other', + value: 'other', + }, + { + name: 'Work', + value: 'work', + }, + ], + description: 'Location for this email address', + default: '', + }, + { + displayName: 'Value', + name: 'value', + type: 'string', + default: '', + description: 'Email', + }, + ], + }, + ], + }, + { + displayName: 'Phones', + name: 'phonesUi', + placeholder: 'Add Phone', + type: 'fixedCollection', + typeOptions: { + multipleValues: true, + }, + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'customer', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Email', + name: 'phonesValues', + values: [ + { + displayName: 'Type', + name: 'type', + type: 'options', + options: [ + { + name: 'Fax', + value: 'fax', + }, + { + name: 'Home', + value: 'home', + }, + { + name: 'Other', + value: 'other', + }, + { + name: 'Pager', + value: 'pager', + }, + { + name: 'Work', + value: 'work', + }, + ], + description: 'Location for this phone', + default: '', + }, + { + displayName: 'Value', + name: 'value', + type: 'string', + default: '', + description: 'Phone', + }, + ], + }, + ], + }, + { + displayName: 'Social Profiles', + name: 'socialProfilesUi', + placeholder: 'Add Social Profile', + type: 'fixedCollection', + typeOptions: { + multipleValues: true, + }, + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'customer', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Social Profile', + name: 'socialProfilesValues', + values: [ + { + displayName: 'Type', + name: 'type', + type: 'options', + options: [ + { + name: 'About Me', + value: 'aboutMe', + }, + { + name: 'Facebook', + value: 'facebook', + }, + { + name: 'Flickr', + value: 'flickr', + }, + { + name: 'Forsquare', + value: 'forsquare', + }, + { + name: 'Google', + value: 'google', + }, + { + name: 'Google Plus', + value: 'googleplus', + }, + { + name: 'Linkedin', + value: 'linkedin', + }, + { + name: 'Other', + value: 'other', + }, + { + name: 'Quora', + value: 'quora', + }, + { + name: 'Tungleme', + value: 'tungleme', + }, + { + name: 'Twitter', + value: 'twitter', + }, + { + name: 'Youtube', + value: 'youtube', + }, + ], + description: 'Type of social profile', + default: '', + }, + { + displayName: 'Value', + name: 'value', + type: 'string', + default: '', + description: 'Social Profile handle (url for example)', + }, + ], + }, + ], + }, + { + displayName: 'Websites', + name: 'websitesUi', + placeholder: 'Add Website', + type: 'fixedCollection', + typeOptions: { + multipleValues: true, + }, + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'customer', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Website', + name: 'websitesValues', + values: [ + { + displayName: 'Value', + name: 'value', + type: 'string', + default: '', + description: 'Website URL', + }, + ], + }, + ], + }, +/* -------------------------------------------------------------------------- */ +/* customer:getAll */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'customer', + ], + }, + }, + default: false, + description: 'If all results should be returned or only up to a given limit.', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'customer', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + }, + default: 50, + description: 'How many results to return.', + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + displayOptions: { + show: { + resource: [ + 'customer', + ], + operation: [ + 'getAll', + ], + }, + }, + options: [ + { + displayName: 'First Name', + name: 'firstName', + type: 'string', + default: '', + description: 'Filters customers by first name', + }, + { + displayName: 'Last Name', + name: 'lastName', + type: 'string', + default: '', + description: 'Filters customers by last name', + }, + { + displayName: 'Mailbox ID', + name: 'mailbox', + type: 'string', + default: '', + description: 'Filters customers from a specific mailbox', + }, + { + displayName: 'Modified Since', + name: 'modifiedSince', + type: 'dateTime', + default: '', + description: 'Returns only customers that were modified after this date', + }, + { + displayName: 'Sort Field', + name: 'sortField', + type: 'options', + options: [ + { + name: 'Score', + value: 'score', + }, + { + name: 'First Name', + value: 'firstName', + }, + { + name: 'Last Name', + value: 'lastName', + }, + { + name: 'Modified At', + value: 'modifiedAt', + }, + ], + default: 'score', + description: 'Sorts the result by specified field', + }, + { + displayName: 'Sort Order', + name: 'sortOrder', + type: 'options', + options: [ + { + name: 'ASC', + value: 'asc', + }, + { + name: 'DESC', + value: 'desc', + }, + ], + default: 'desc', + }, + { + displayName: 'Query', + name: 'query', + type: 'string', + typeOptions: { + alwaysOpenEditWindow: true, + }, + default: '', + description: 'Advanced search Examples' + }, + ], + }, +/* -------------------------------------------------------------------------- */ +/* customer:get */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Customer ID', + name: 'customerId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + resource: [ + 'customer', + ], + operation: [ + 'get', + ], + }, + }, + description: 'Customer ID', + }, +/* -------------------------------------------------------------------------- */ +/* customer:update */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Customer ID', + name: 'customerId', + type: 'string', + default: '', + displayOptions: { + show: { + operation: [ + 'update', + ], + resource: [ + 'customer', + ], + }, + }, + description: 'Customer ID', + }, + { + displayName: 'Update Fields', + name: 'updateFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + operation: [ + 'update', + ], + resource: [ + 'customer', + ], + }, + }, + options: [ + { + displayName: 'Age', + name: 'age', + type: 'number', + typeOptions: { + minValue: 1, + }, + default: 1, + description: `Customer’s age`, + }, + { + displayName: 'First Name', + name: 'firstName', + type: 'string', + default: '', + description: `First name of the customer. When defined it must be between 1 and 40 characters.`, + }, + { + displayName: 'Gender', + name: 'gender', + type: 'options', + options: [ + { + name: 'Female', + value: 'female', + }, + { + name: 'Male', + value: 'male', + }, + { + name: 'Unknown', + value: 'unknown', + }, + ], + default: '', + description: 'Gender of this customer.', + }, + { + displayName: 'Job Title', + name: 'jobTitle', + type: 'string', + default: '', + description: 'Job title. Max length 60 characters.', + }, + { + displayName: 'Last Name', + name: 'lastName', + type: 'string', + default: '', + description: 'Last name of the customer', + }, + { + displayName: 'Location', + name: 'location', + type: 'string', + default: '', + description: 'Location of the customer.', + }, + { + displayName: 'Notes', + name: 'background', + type: 'string', + typeOptions: { + alwaysOpenEditWindow: true, + }, + default: '', + description: `Notes`, + }, + { + displayName: 'Organization', + name: 'organization', + type: 'string', + default: '', + description: 'Organization', + }, + { + displayName: 'Photo Url', + name: 'photoUrl', + type: 'string', + default: '', + description: 'URL of the customer’s photo', + }, + ], + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/HelpScout/CustomerInterface.ts b/packages/nodes-base/nodes/HelpScout/CustomerInterface.ts new file mode 100644 index 0000000000..fe50a7cb3c --- /dev/null +++ b/packages/nodes-base/nodes/HelpScout/CustomerInterface.ts @@ -0,0 +1,20 @@ +import { IDataObject } from 'n8n-workflow'; + +export interface ICustomer { + address?: IDataObject; + age?: string; + background?: string; + chats?: IDataObject[]; + emails?: IDataObject[]; + firstName?: string; + gender?: string; + jobTitle?: string; + lastName?: string; + location?: string; + organization?: string; + phones?: IDataObject[]; + photoUrl?: string; + properties?: IDataObject; + socialProfiles?: IDataObject[]; + websites?: IDataObject[]; +} diff --git a/packages/nodes-base/nodes/HelpScout/GenericFunctions.ts b/packages/nodes-base/nodes/HelpScout/GenericFunctions.ts new file mode 100644 index 0000000000..76b64ed3e7 --- /dev/null +++ b/packages/nodes-base/nodes/HelpScout/GenericFunctions.ts @@ -0,0 +1,72 @@ +import { OptionsWithUri } from 'request'; +import { + IHookFunctions, + IExecuteFunctions, + IExecuteSingleFunctions, + ILoadOptionsFunctions, +} from 'n8n-core'; +import { + IDataObject, +} from 'n8n-workflow'; + +import { + get, +} from 'lodash'; + +export async function helpscoutApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions | IHookFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any + let options: OptionsWithUri = { + headers: { + 'Content-Type': 'application/json', + }, + method, + body, + qs, + uri: uri || `https://api.helpscout.net${resource}`, + json: true + }; + try { + if (Object.keys(option).length !== 0) { + options = Object.assign({}, options, option); + } + if (Object.keys(body).length === 0) { + delete options.body; + } + //@ts-ignore + return await this.helpers.requestOAuth.call(this, 'helpScoutOAuth2Api', options); + } catch (error) { + if (error.response && error.response.body + && error.response.body._embedded + && error.response.body._embedded.errors) { + // Try to return the error prettier + //@ts-ignore + throw new Error(`HelpScout error response [${error.statusCode}]: ${error.response.body.message} - ${error.response.body._embedded.errors.map(error => { + return `${error.path} ${error.message}`; + }).join('-')}`); + } + + throw new Error(`HelpScout error response [${error.statusCode}]: ${error.message}`); + } +} + +export async function helpscoutApiRequestAllItems(this: IExecuteFunctions | ILoadOptionsFunctions | IHookFunctions, propertyName: string ,method: string, endpoint: string, body: any = {}, query: IDataObject = {}): Promise { // tslint:disable-line:no-any + + const returnData: IDataObject[] = []; + + let responseData; + let uri; + + do { + responseData = await helpscoutApiRequest.call(this, method, endpoint, body, query, uri); + uri = get(responseData, '_links.next.href'); + returnData.push.apply(returnData, get(responseData, propertyName)); + if (query.limit && query.limit <= returnData.length) { + return returnData; + } + } while ( + responseData['_links'] !== undefined && + responseData['_links'].next !== undefined && + responseData['_links'].next.href !== undefined + ); + + return returnData; +} diff --git a/packages/nodes-base/nodes/HelpScout/HelpScout.node.ts b/packages/nodes-base/nodes/HelpScout/HelpScout.node.ts new file mode 100644 index 0000000000..ce1f02d648 --- /dev/null +++ b/packages/nodes-base/nodes/HelpScout/HelpScout.node.ts @@ -0,0 +1,439 @@ +import { + IExecuteFunctions, +} from 'n8n-core'; + +import { + IBinaryKeyData, + IDataObject, + ILoadOptionsFunctions, + INodeExecutionData, + INodePropertyOptions, + INodeTypeDescription, + INodeType, +} from 'n8n-workflow'; + +import { + countriesCodes +} from './CountriesCodes'; + +import { + conversationFields, + conversationOperations, +} from './ConversationDescription'; + +import { + customerFields, + customerOperations, +} from './CustomerDescription'; + +import { + ICustomer, +} from './CustomerInterface'; + +import { + IConversation, +} from './ConversationInterface'; + +import { + helpscoutApiRequest, + helpscoutApiRequestAllItems, +} from './GenericFunctions'; + +import { + mailboxFields, + mailboxOperations, +} from './MailboxDescription'; + +import { + threadFields, + threadOperations, +} from './ThreadDescription'; + +import { + IAttachment, + IThread, +} from './ThreadInterface'; + + +export class HelpScout implements INodeType { + description: INodeTypeDescription = { + displayName: 'HelpScout', + name: 'helpScout', + icon: 'file:helpScout.png', + group: ['input'], + version: 1, + subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', + description: 'Consume Help Scout API.', + defaults: { + name: 'HelpScout', + color: '#1392ee', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'helpScoutOAuth2Api', + required: true, + }, + ], + properties: [ + { + displayName: 'Resource', + name: 'resource', + type: 'options', + options: [ + { + name: 'Conversation', + value: 'conversation', + }, + { + name: 'Customer', + value: 'customer', + }, + { + name: 'Mailbox', + value: 'mailbox', + }, + { + name: 'Thread', + value: 'thread', + }, + ], + default: 'conversation', + description: 'The resource to operate on.', + }, + ...conversationOperations, + ...conversationFields, + ...customerOperations, + ...customerFields, + ...mailboxOperations, + ...mailboxFields, + ...threadOperations, + ...threadFields, + ], + }; + + methods = { + loadOptions: { + // Get all the countries codes to display them to user so that he can + // select them easily + async getCountriesCodes(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + for (const countryCode of countriesCodes) { + const countryCodeName = `${countryCode.name} - ${countryCode.alpha2}`; + const countryCodeId = countryCode.alpha2; + returnData.push({ + name: countryCodeName, + value: countryCodeId, + }); + } + return returnData; + }, + // Get all the tags to display them to user so that he can + // select them easily + async getTags(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + const tags = await helpscoutApiRequestAllItems.call(this, '_embedded.tags', 'GET', '/v2/tags'); + for (const tag of tags) { + const tagName = tag.name; + const tagId = tag.id; + returnData.push({ + name: tagName, + value: tagId, + }); + } + return returnData; + }, + // Get all the mailboxes to display them to user so that he can + // select them easily + async getMailboxes(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + const mailboxes = await helpscoutApiRequestAllItems.call(this, '_embedded.mailboxes', 'GET', '/v2/mailboxes'); + for (const mailbox of mailboxes) { + const mailboxName = mailbox.name; + const mailboxId = mailbox.id; + returnData.push({ + name: mailboxName, + value: mailboxId, + }); + } + return returnData; + }, + }, + }; + + async execute(this: IExecuteFunctions): Promise { + const items = this.getInputData(); + const returnData: IDataObject[] = []; + const length = items.length as unknown as number; + const qs: IDataObject = {}; + let responseData; + const resource = this.getNodeParameter('resource', 0) as string; + const operation = this.getNodeParameter('operation', 0) as string; + for (let i = 0; i < length; i++) { + if (resource === 'conversation') { + //https://developer.helpscout.com/mailbox-api/endpoints/conversations/create + if (operation === 'create') { + const mailboxId = this.getNodeParameter('mailboxId', i) as number; + const status = this.getNodeParameter('status', i) as string; + const subject = this.getNodeParameter('subject', i) as string; + const type = this.getNodeParameter('type', i) as string; + const resolveData = this.getNodeParameter('resolveData', i) as boolean; + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + const threads = (this.getNodeParameter('threadsUi', i) as IDataObject).threadsValues as IDataObject[]; + const body: IConversation = { + mailboxId, + status, + subject, + type, + }; + Object.assign(body, additionalFields); + if (additionalFields.customerId) { + body.customer = { + id: additionalFields.customerId, + }; + //@ts-ignore + delete body.customerId; + } + if (additionalFields.customerEmail) { + body.customer = { + email: additionalFields.customerEmail, + }; + //@ts-ignore + delete body.customerEmail; + } + if (body.customer === undefined) { + throw new Error('Either customer email or customer ID must be set'); + } + if (threads) { + for (let i = 0; i < threads.length; i++) { + if (threads[i].type === '' || threads[i].text === '') { + throw new Error('Chat Threads cannot be empty'); + } + if (threads[i].type !== 'note') { + threads[i].customer = body.customer; + } + } + body.threads = threads; + } + responseData = await helpscoutApiRequest.call(this, 'POST', '/v2/conversations', body, qs, undefined, { resolveWithFullResponse: true }); + const id = responseData.headers['resource-id']; + const uri = responseData.headers.location; + if (resolveData) { + responseData = await helpscoutApiRequest.call(this, 'GET', '', {}, {}, uri); + } else { + responseData = { + id, + uri, + }; + } + } + //https://developer.helpscout.com/mailbox-api/endpoints/conversations/delete + if (operation === 'delete') { + const conversationId = this.getNodeParameter('conversationId', i) as string; + responseData = await helpscoutApiRequest.call(this, 'DELETE', `/v2/conversations/${conversationId}`); + responseData = { success: true }; + } + //https://developer.helpscout.com/mailbox-api/endpoints/conversations/get + if (operation === 'get') { + const conversationId = this.getNodeParameter('conversationId', i) as string; + responseData = await helpscoutApiRequest.call(this, 'GET', `/v2/conversations/${conversationId}`); + } + //https://developer.helpscout.com/mailbox-api/endpoints/conversations/list + if (operation === 'getAll') { + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + const options = this.getNodeParameter('options', i) as IDataObject; + Object.assign(qs, options); + if (returnAll) { + responseData = await helpscoutApiRequestAllItems.call(this, '_embedded.conversations', 'GET', '/v2/conversations', {}, qs); + } else { + qs.limit = this.getNodeParameter('limit', i) as number; + responseData = await helpscoutApiRequestAllItems.call(this, '_embedded.conversations', 'GET', '/v2/conversations', {}, qs); + responseData = responseData.splice(0, qs.limit); + } + } + } + if (resource === 'customer') { + //https://developer.helpscout.com/mailbox-api/endpoints/customers/create + if (operation === 'create') { + const resolveData = this.getNodeParameter('resolveData', i) as boolean; + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + const chats = (this.getNodeParameter('chatsUi', i) as IDataObject).chatsValues as IDataObject[]; + const address = (this.getNodeParameter('addressUi', i) as IDataObject).addressValue as IDataObject; + const emails = (this.getNodeParameter('emailsUi', i) as IDataObject).emailsValues as IDataObject[]; + const phones = (this.getNodeParameter('phonesUi', i) as IDataObject).phonesValues as IDataObject[]; + const socialProfiles = (this.getNodeParameter('socialProfilesUi', i) as IDataObject).socialProfilesValues as IDataObject[]; + const websites = (this.getNodeParameter('websitesUi', i) as IDataObject).websitesValues as IDataObject[]; + let body: ICustomer = {}; + body = Object.assign({}, additionalFields); + if (body.age) { + body.age = body.age.toString(); + } + if (chats) { + body.chats = chats; + } + if (address) { + body.address = address; + body.address.lines = [address.line1, address.line2]; + } + if (emails) { + body.emails = emails; + } + if (phones) { + body.phones = phones; + } + if (socialProfiles) { + body.socialProfiles = socialProfiles; + } + if (websites) { + body.websites = websites; + } + if (Object.keys(body).length === 0) { + throw new Error('You have to set at least one field'); + } + responseData = await helpscoutApiRequest.call(this, 'POST', '/v2/customers', body, qs, undefined, { resolveWithFullResponse: true }); + const id = responseData.headers['resource-id']; + const uri = responseData.headers.location; + if (resolveData) { + responseData = await helpscoutApiRequest.call(this, 'GET', '', {}, {}, uri); + } else { + responseData = { + id, + uri, + }; + } + } + //https://developer.helpscout.com/mailbox-api/endpoints/customer_properties/list + if (operation === 'properties') { + responseData = await helpscoutApiRequestAllItems.call(this, '_embedded.customer-properties', 'GET', '/v2/customer-properties', {}, qs); + } + //https://developer.helpscout.com/mailbox-api/endpoints/customers/get + if (operation === 'get') { + const customerId = this.getNodeParameter('customerId', i) as string; + responseData = await helpscoutApiRequest.call(this, 'GET', `/v2/customers/${customerId}`); + } + //https://developer.helpscout.com/mailbox-api/endpoints/customers/list + if (operation === 'getAll') { + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + const options = this.getNodeParameter('options', i) as IDataObject; + Object.assign(qs, options); + if (returnAll) { + responseData = await helpscoutApiRequestAllItems.call(this, '_embedded.customers', 'GET', '/v2/customers', {}, qs); + } else { + qs.limit = this.getNodeParameter('limit', i) as number; + responseData = await helpscoutApiRequestAllItems.call(this, '_embedded.customers', 'GET', '/v2/customers', {}, qs); + responseData = responseData.splice(0, qs.limit); + } + } + //https://developer.helpscout.com/mailbox-api/endpoints/customers/overwrite/ + if (operation === 'update') { + const customerId = this.getNodeParameter('customerId', i) as string; + const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; + let body: ICustomer = {}; + body = Object.assign({}, updateFields); + if (body.age) { + body.age = body.age.toString(); + } + if (Object.keys(body).length === 0) { + throw new Error('You have to set at least one field'); + } + responseData = await helpscoutApiRequest.call(this, 'PUT', `/v2/customers/${customerId}`, body, qs, undefined, { resolveWithFullResponse: true }); + responseData = { success: true }; + } + } + if (resource === 'mailbox') { + //https://developer.helpscout.com/mailbox-api/endpoints/mailboxes/get + if (operation === 'get') { + const mailboxId = this.getNodeParameter('mailboxId', i) as string; + responseData = await helpscoutApiRequest.call(this, 'GET', `/v2/mailboxes/${mailboxId}`, {}, qs); + } + //https://developer.helpscout.com/mailbox-api/endpoints/mailboxes/list + if (operation === 'getAll') { + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + if (returnAll) { + responseData = await helpscoutApiRequestAllItems.call(this, '_embedded.mailboxes', 'GET', '/v2/mailboxes', {}, qs); + } else { + qs.limit = this.getNodeParameter('limit', i) as number; + responseData = await helpscoutApiRequestAllItems.call(this, '_embedded.mailboxes', 'GET', '/v2/mailboxes', {}, qs); + responseData = responseData.splice(0, qs.limit); + } + } + } + if (resource === 'thread') { + //https://developer.helpscout.com/mailbox-api/endpoints/conversations/threads/chat + if (operation === 'create') { + const conversationId = this.getNodeParameter('conversationId', i) as string; + const type = this.getNodeParameter('type', i) as string; + const text = this.getNodeParameter('text', i) as string; + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + const attachments = this.getNodeParameter('attachmentsUi', i) as IDataObject; + const body: IThread = { + text, + attachments: [], + }; + Object.assign(body, additionalFields); + if (additionalFields.customerId) { + body.customer = { + id: additionalFields.customerId, + }; + //@ts-ignore + delete body.customerId; + } + if (additionalFields.customerEmail) { + body.customer = { + email: additionalFields.customerEmail, + }; + //@ts-ignore + delete body.customerEmail; + } + if (body.customer === undefined) { + throw new Error('Either customer email or customer ID must be set'); + } + if (attachments) { + if (attachments.attachmentsValues + && (attachments.attachmentsValues as IDataObject[]).length !== 0) { + body.attachments?.push.apply(body.attachments, attachments.attachmentsValues as IAttachment[]); + } + if (attachments.attachmentsBinary + && (attachments.attachmentsBinary as IDataObject[]).length !== 0 + && items[i].binary) { + const mapFunction = (value: IDataObject): IAttachment => { + const binaryProperty = (items[i].binary as IBinaryKeyData)[value.property as string]; + if (binaryProperty) { + return { + fileName: binaryProperty.fileName || 'unknown', + data: binaryProperty.data, + mimeType: binaryProperty.mimeType, + }; + } else { + throw new Error(`Binary property ${value.property} does not exist on input`); + } + }; + body.attachments?.push.apply(body.attachments, (attachments.attachmentsBinary as IDataObject[]).map(mapFunction) as IAttachment[]); + } + } + responseData = await helpscoutApiRequest.call(this, 'POST', `/v2/conversations/${conversationId}/chats`, body); + responseData = { success: true }; + } + //https://developer.helpscout.com/mailbox-api/endpoints/conversations/threads/list + if (operation === 'getAll') { + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + const conversationId = this.getNodeParameter('conversationId', i) as string; + if (returnAll) { + responseData = await helpscoutApiRequestAllItems.call(this, '_embedded.threads', 'GET', `/v2/conversations/${conversationId}/threads`); + } else { + qs.limit = this.getNodeParameter('limit', i) as number; + responseData = await helpscoutApiRequestAllItems.call(this, '_embedded.threads', 'GET', `/v2/conversations/${conversationId}/threads`, {}, qs); + responseData = responseData.splice(0, qs.limit); + } + } + } + if (Array.isArray(responseData)) { + returnData.push.apply(returnData, responseData as IDataObject[]); + } else if (responseData !== undefined) { + returnData.push(responseData as IDataObject); + } + } + return [this.helpers.returnJsonArray(returnData)]; + } +} diff --git a/packages/nodes-base/nodes/HelpScout/HelpScoutTrigger.node.ts b/packages/nodes-base/nodes/HelpScout/HelpScoutTrigger.node.ts new file mode 100644 index 0000000000..63592aa14e --- /dev/null +++ b/packages/nodes-base/nodes/HelpScout/HelpScoutTrigger.node.ts @@ -0,0 +1,203 @@ +import { + IHookFunctions, + IWebhookFunctions, +} from 'n8n-core'; + +import { + IDataObject, + INodeType, + INodeTypeDescription, + IWebhookResponseData, +} from 'n8n-workflow'; + +import { + helpscoutApiRequest, + helpscoutApiRequestAllItems, +} from './GenericFunctions'; + +import { createHmac } from 'crypto'; + +export class HelpScoutTrigger implements INodeType { + description: INodeTypeDescription = { + displayName: 'HelpScout Trigger', + name: 'helpScoutTrigger', + icon: 'file:helpScout.png', + group: ['trigger'], + version: 1, + description: 'Starts the workflow when HelpScout events occure.', + defaults: { + name: 'HelpScout Trigger', + color: '#1392ee', + }, + inputs: [], + outputs: ['main'], + credentials: [ + { + name: 'helpScoutOAuth2Api', + required: true, + }, + ], + webhooks: [ + { + name: 'default', + httpMethod: 'POST', + responseMode: 'onReceived', + path: 'webhook', + }, + ], + properties: [ + { + displayName: 'Events', + name: 'events', + type: 'multiOptions', + options: [ + + { + name: 'Conversation - Assigned', + value: 'convo.assigned', + }, + { + name: 'Conversation - Created', + value: 'convo.created', + }, + { + name: 'Conversation - Deleted', + value: 'convo.deleted', + }, + { + name: 'Conversation - Merged', + value: 'convo.merged', + }, + { + name: 'Conversation - Moved', + value: 'convo.moved', + }, + { + name: 'Conversation - Status', + value: 'convo.status', + }, + { + name: 'Conversation - Tags', + value: 'convo.tags', + }, + { + name: 'Conversation Agent Reply - Created', + value: 'convo.agent.reply.created', + }, + { + name: 'Conversation Customer Reply - Created', + value: 'convo.customer.reply.created', + }, + { + name: 'Conversation Note - Created', + value: 'convo.note.created', + }, + { + name: 'Customer - Created', + value: 'customer.created', + }, + { + name: 'Rating - Received', + value: 'satisfaction.ratings', + }, + ], + default: [], + required: true, + }, + ], + + }; + + // @ts-ignore (because of request) + webhookMethods = { + default: { + async checkExists(this: IHookFunctions): Promise { + const webhookUrl = this.getNodeWebhookUrl('default'); + const webhookData = this.getWorkflowStaticData('node'); + const events = this.getNodeParameter('events') as string; + + // Check all the webhooks which exist already if it is identical to the + // one that is supposed to get created. + const endpoint = '/v2/webhooks'; + const data = await helpscoutApiRequestAllItems.call(this, '_embedded.webhooks', 'GET', endpoint, {}); + + for (const webhook of data) { + if (webhook.url === webhookUrl) { + for (const event of events) { + if (!webhook.events.includes(event) + && webhook.state === 'enabled') { + return false; + } + } + } + // Set webhook-id to be sure that it can be deleted + webhookData.webhookId = webhook.id as string; + return true; + } + return false; + }, + async create(this: IHookFunctions): Promise { + const webhookData = this.getWorkflowStaticData('node'); + const webhookUrl = this.getNodeWebhookUrl('default'); + const events = this.getNodeParameter('events') as string; + + const endpoint = '/v2/webhooks'; + + const body = { + url: webhookUrl, + events, + secret: Math.random().toString(36).substring(2, 15), + }; + + const responseData = await helpscoutApiRequest.call(this, 'POST', endpoint, body, {}, undefined, { resolveWithFullResponse: true }); + + if (responseData.headers['resource-id'] === undefined) { + // Required data is missing so was not successful + return false; + } + + webhookData.webhookId = responseData.headers['resource-id'] as string; + webhookData.secret = body.secret; + return true; + }, + async delete(this: IHookFunctions): Promise { + const webhookData = this.getWorkflowStaticData('node'); + if (webhookData.webhookId !== undefined) { + + const endpoint = `/v2/webhooks/${webhookData.webhookId}`; + try { + await helpscoutApiRequest.call(this, 'DELETE', endpoint); + } catch (e) { + return false; + } + + // Remove from the static workflow data so that it is clear + // that no webhooks are registred anymore + delete webhookData.webhookId; + delete webhookData.secret; + } + return true; + }, + }, + }; + + async webhook(this: IWebhookFunctions): Promise { + const req = this.getRequestObject(); + const bodyData = this.getBodyData(); + const headerData = this.getHeaderData() as IDataObject; + const webhookData = this.getWorkflowStaticData('node'); + if (headerData['x-helpscout-signature'] === undefined) { + return {}; + } + //@ts-ignore + const computedSignature = createHmac('sha1', webhookData.secret as string).update(req.rawBody).digest('base64'); + if (headerData['x-helpscout-signature'] !== computedSignature) { + return {}; + } + return { + workflowData: [ + this.helpers.returnJsonArray(bodyData), + ], + }; + } +} diff --git a/packages/nodes-base/nodes/HelpScout/MailboxDescription.ts b/packages/nodes-base/nodes/HelpScout/MailboxDescription.ts new file mode 100644 index 0000000000..d9aff61100 --- /dev/null +++ b/packages/nodes-base/nodes/HelpScout/MailboxDescription.ts @@ -0,0 +1,97 @@ +import { INodeProperties } from 'n8n-workflow'; + +export const mailboxOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'mailbox', + ], + }, + }, + options: [ + { + name: 'Get', + value: 'get', + description: 'Get data of a mailbox', + }, + { + name: 'Get All', + value: 'getAll', + description: 'Get all mailboxes', + }, + ], + default: 'get', + description: 'The operation to perform.', + }, +] as INodeProperties[]; + +export const mailboxFields = [ + +/* -------------------------------------------------------------------------- */ +/* mailbox:get */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Mailbox ID', + name: 'mailboxId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + resource: [ + 'mailbox', + ], + operation: [ + 'get', + ], + }, + }, + }, +/* -------------------------------------------------------------------------- */ +/* mailbox:getAll */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'mailbox', + ], + }, + }, + default: false, + description: 'If all results should be returned or only up to a given limit.', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'mailbox', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + }, + default: 50, + description: 'How many results to return.', + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/HelpScout/ThreadDescription.ts b/packages/nodes-base/nodes/HelpScout/ThreadDescription.ts new file mode 100644 index 0000000000..eb2039745c --- /dev/null +++ b/packages/nodes-base/nodes/HelpScout/ThreadDescription.ts @@ -0,0 +1,297 @@ +import { INodeProperties } from 'n8n-workflow'; + +export const threadOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'thread', + ], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create a new chat thread', + }, + { + name: 'Get All', + value: 'getAll', + description: 'Get all chat threads', + }, + ], + default: 'create', + description: 'The operation to perform.', + }, +] as INodeProperties[]; + +export const threadFields = [ +/* -------------------------------------------------------------------------- */ +/* thread:create */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Conversation ID', + name: 'conversationId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + resource: [ + 'thread', + ], + operation: [ + 'create', + ], + }, + }, + description: 'conversation ID', + }, + { + displayName: 'Type', + name: 'type', + type: 'options', + required: true, + displayOptions: { + show: { + resource: [ + 'thread', + ], + operation: [ + 'create', + ], + }, + }, + options: [ + { + name: 'Chat', + value: 'chat' + }, + { + name: 'Customer', + value: 'customer' + }, + { + name: 'Note', + value: 'note' + }, + { + name: 'Phone', + value: 'phone' + }, + { + name: 'Reply', + value: 'reply' + }, + ], + default: '', + }, + { + displayName: 'Text', + name: 'text', + type: 'string', + default: '', + typeOptions: { + alwaysOpenEditWindow: true, + }, + required: true, + displayOptions: { + show: { + resource: [ + 'thread', + ], + operation: [ + 'create', + ], + }, + }, + description: 'The chat text', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'thread', + ], + }, + }, + options: [ + { + displayName: 'Created At', + name: 'createdAt', + type: 'dateTime', + default: '', + }, + { + displayName: 'Customer Email', + name: 'customerEmail', + type: 'string', + default: '', + }, + { + displayName: 'Customer ID', + name: 'customerId', + type: 'number', + default: 0, + }, + { + displayName: 'Draft', + name: 'draft', + type: 'boolean', + default: false, + displayOptions: { + show: { + '/type': [ + 'note', + ], + }, + }, + description: 'If set to true, a draft reply is created', + }, + { + displayName: 'Imported', + name: 'imported', + type: 'boolean', + default: false, + description: 'When imported is set to true, no outgoing emails or notifications will be generated.', + }, + ] + }, + { + displayName: 'Attachments', + name: 'attachmentsUi', + placeholder: 'Add Attachments', + type: 'fixedCollection', + typeOptions: { + multipleValues: true, + }, + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'thread', + ], + }, + }, + options: [ + { + name: 'attachmentsValues', + displayName: 'Attachments Values', + values: [ + { + displayName: 'FileName', + name: 'fileName', + type: 'string', + default: '', + description: 'Attachment’s file name', + }, + { + displayName: 'Mime Type', + name: 'mimeType', + type: 'string', + default: '', + description: 'Attachment’s mime type', + }, + { + displayName: 'Data', + name: 'data', + type: 'string', + default: '', + placeholder: 'ZXhhbXBsZSBmaWxl', + description: 'Base64-encoded stream of data.', + }, + ], + }, + { + name: 'attachmentsBinary', + displayName: 'Attachments Binary', + values: [ + { + displayName: 'Property', + name: 'property', + type: 'string', + default: 'data', + description: 'Name of the binary properties which contain data which should be added to email as attachment', + }, + ], + }, + ], + default: '', + description: 'Array of supported attachments to add to the message.', + }, +/* -------------------------------------------------------------------------- */ +/* thread:getAll */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Conversation ID', + name: 'conversationId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + resource: [ + 'thread', + ], + operation: [ + 'getAll', + ], + }, + }, + description: 'conversation ID', + }, + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'thread', + ], + }, + }, + default: false, + description: 'If all results should be returned or only up to a given limit.', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'thread', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + }, + default: 50, + description: 'How many results to return.', + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/HelpScout/ThreadInterface.ts b/packages/nodes-base/nodes/HelpScout/ThreadInterface.ts new file mode 100644 index 0000000000..e47a0a6747 --- /dev/null +++ b/packages/nodes-base/nodes/HelpScout/ThreadInterface.ts @@ -0,0 +1,15 @@ +import { IDataObject } from 'n8n-workflow'; + +export interface IAttachment { + fileName?: string; + mimeType?: string; + data?: string; +} + +export interface IThread { + createdAt?: string; + customer?: IDataObject; + imported?: boolean; + text?: string; + attachments?: IAttachment[]; +} diff --git a/packages/nodes-base/nodes/HelpScout/helpScout.png b/packages/nodes-base/nodes/HelpScout/helpScout.png new file mode 100644 index 0000000000..1ebdaa8e5e Binary files /dev/null and b/packages/nodes-base/nodes/HelpScout/helpScout.png differ diff --git a/packages/nodes-base/nodes/Microsoft/Excel/GenericFunctions.ts b/packages/nodes-base/nodes/Microsoft/Excel/GenericFunctions.ts new file mode 100644 index 0000000000..33090090b3 --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/Excel/GenericFunctions.ts @@ -0,0 +1,73 @@ +import { OptionsWithUri } from 'request'; +import { + IExecuteFunctions, + IExecuteSingleFunctions, + ILoadOptionsFunctions, +} from 'n8n-core'; +import { + IDataObject +} from 'n8n-workflow'; + +export async function microsoftApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, headers: IDataObject = {}): Promise { // tslint:disable-line:no-any + const options: OptionsWithUri = { + headers: { + 'Content-Type': 'application/json', + }, + method, + body, + qs, + uri: uri || `https://graph.microsoft.com/v1.0/me${resource}`, + json: true + }; + try { + if (Object.keys(headers).length !== 0) { + options.headers = Object.assign({}, options.headers, headers); + } + //@ts-ignore + return await this.helpers.requestOAuth.call(this, 'microsoftExcelOAuth2Api', options); + } catch (error) { + if (error.response && error.response.body && error.response.body.error && error.response.body.error.message) { + // Try to return the error prettier + throw new Error(`Microsoft error response [${error.statusCode}]: ${error.response.body.error.message}`); + } + throw error; + } +} + +export async function microsoftApiRequestAllItems(this: IExecuteFunctions | ILoadOptionsFunctions, propertyName: string ,method: string, endpoint: string, body: any = {}, query: IDataObject = {}): Promise { // tslint:disable-line:no-any + + const returnData: IDataObject[] = []; + + let responseData; + let uri: string | undefined; + query['$top'] = 100; + + do { + responseData = await microsoftApiRequest.call(this, method, endpoint, body, query, uri); + uri = responseData['@odata.nextLink']; + returnData.push.apply(returnData, responseData[propertyName]); + } while ( + responseData['@odata.nextLink'] !== undefined + ); + + return returnData; +} + +export async function microsoftApiRequestAllItemsSkip(this: IExecuteFunctions | ILoadOptionsFunctions, propertyName: string ,method: string, endpoint: string, body: any = {}, query: IDataObject = {}): Promise { // tslint:disable-line:no-any + + const returnData: IDataObject[] = []; + + let responseData; + query['$top'] = 100; + query['$skip'] = 0; + + do { + responseData = await microsoftApiRequest.call(this, method, endpoint, body, query); + query['$skip'] += query['$top']; + returnData.push.apply(returnData, responseData[propertyName]); + } while ( + responseData['value'].length !== 0 + ); + + return returnData; +} diff --git a/packages/nodes-base/nodes/Microsoft/Excel/MicrosoftExcel.node.ts b/packages/nodes-base/nodes/Microsoft/Excel/MicrosoftExcel.node.ts new file mode 100644 index 0000000000..26e58ef9ad --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/Excel/MicrosoftExcel.node.ts @@ -0,0 +1,421 @@ +import { + IExecuteFunctions, +} from 'n8n-core'; + +import { + IDataObject, + INodeExecutionData, + INodeTypeDescription, + INodeType, + ILoadOptionsFunctions, + INodePropertyOptions, +} from 'n8n-workflow'; + +import { + microsoftApiRequest, + microsoftApiRequestAllItems, + microsoftApiRequestAllItemsSkip, +} from './GenericFunctions'; + +import { + workbookOperations, + workbookFields, +} from './WorkbookDescription'; + +import { + worksheetOperations, + worksheetFields, +} from './WorksheetDescription'; + +import { + tableOperations, + tableFields, +} from './TableDescription'; + +export class MicrosoftExcel implements INodeType { + description: INodeTypeDescription = { + displayName: 'Microsoft Excel', + name: 'microsoftExcel', + icon: 'file:excel.png', + group: ['input'], + version: 1, + subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', + description: 'Consume Microsoft Excel API.', + defaults: { + name: 'Microsoft Excel', + color: '#1c6d40', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'microsoftExcelOAuth2Api', + required: true, + }, + ], + properties: [ + { + displayName: 'Resource', + name: 'resource', + type: 'options', + options: [ + { + name: 'Table', + value: 'table', + description: 'Represents an Excel table.', + }, + { + name: 'Workbook', + value: 'workbook', + description: 'Workbook is the top level object which contains related workbook objects such as worksheets, tables, ranges, etc.', + }, + { + name: 'Worksheet', + value: 'worksheet', + description: 'An Excel worksheet is a grid of cells. It can contain data, tables, charts, etc.', + }, + ], + default: 'workbook', + description: 'The resource to operate on.', + }, + ...workbookOperations, + ...workbookFields, + ...worksheetOperations, + ...worksheetFields, + ...tableOperations, + ...tableFields, + ], + }; + + methods = { + loadOptions: { + // Get all the workbooks to display them to user so that he can + // select them easily + async getWorkbooks(this: ILoadOptionsFunctions): Promise { + const qs: IDataObject = { + select: 'id,name', + }; + const returnData: INodePropertyOptions[] = []; + const workbooks = await microsoftApiRequestAllItems.call(this, 'value', 'GET', `/drive/root/search(q='.xlsx')`, {}, qs); + for (const workbook of workbooks) { + const workbookName = workbook.name; + const workbookId = workbook.id; + returnData.push({ + name: workbookName, + value: workbookId, + }); + } + return returnData; + }, + // Get all the worksheets to display them to user so that he can + // select them easily + async getworksheets(this: ILoadOptionsFunctions): Promise { + const workbookId = this.getCurrentNodeParameter('workbook'); + const qs: IDataObject = { + select: 'id,name', + }; + const returnData: INodePropertyOptions[] = []; + const worksheets = await microsoftApiRequestAllItems.call(this, 'value', 'GET', `/drive/items/${workbookId}/workbook/worksheets`, {}, qs); + for (const worksheet of worksheets) { + const worksheetName = worksheet.name; + const worksheetId = worksheet.id; + returnData.push({ + name: worksheetName, + value: worksheetId, + }); + } + return returnData; + }, + // Get all the tables to display them to user so that he can + // select them easily + async getTables(this: ILoadOptionsFunctions): Promise { + const workbookId = this.getCurrentNodeParameter('workbook'); + const worksheetId = this.getCurrentNodeParameter('worksheet'); + const qs: IDataObject = { + select: 'id,name', + }; + const returnData: INodePropertyOptions[] = []; + const tables = await microsoftApiRequestAllItems.call(this, 'value', 'GET', `/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/tables`, {}, qs); + for (const table of tables) { + const tableName = table.name; + const tableId = table.id; + returnData.push({ + name: tableName, + value: tableId, + }); + } + return returnData; + }, + } + }; + + async execute(this: IExecuteFunctions): Promise { + const items = this.getInputData(); + const returnData: IDataObject[] = []; + const length = items.length as unknown as number; + let qs: IDataObject = {}; + const result: IDataObject[] = []; + let responseData; + const resource = this.getNodeParameter('resource', 0) as string; + const operation = this.getNodeParameter('operation', 0) as string; + + if (resource === 'table') { + //https://docs.microsoft.com/en-us/graph/api/table-post-rows?view=graph-rest-1.0&tabs=http + if (operation === 'addRow') { + // TODO: At some point it should be possible to use item dependent parameters. + // Is however important to then not make one separate request each. + const workbookId = this.getNodeParameter('workbook', 0) as string; + const worksheetId = this.getNodeParameter('worksheet', 0) as string; + const tableId = this.getNodeParameter('table', 0) as string; + const additionalFields = this.getNodeParameter('additionalFields', 0) as IDataObject; + const body: IDataObject = {}; + + if (additionalFields.index) { + body.index = additionalFields.index as number; + } + + // Get table columns to eliminate any columns not needed on the input + responseData = await microsoftApiRequest.call(this, 'GET', `/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/tables/${tableId}/columns`, {}, qs); + const columns = responseData.value.map((column: IDataObject) => (column.name)); + + const rows: any[][] = []; // tslint:disable-line:no-any + + // Bring the items into the correct format + for (const item of items) { + const row = []; + for (const column of columns) { + row.push(item.json[column]); + } + rows.push(row); + } + + body.values = rows; + const { id } = await microsoftApiRequest.call(this, 'POST', `/drive/items/${workbookId}/workbook/createSession`, { persistChanges: true }); + responseData = await microsoftApiRequest.call(this, 'POST', `/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/tables/${tableId}/rows/add`, body, {}, '', { 'workbook-session-id': id }); + await microsoftApiRequest.call(this, 'POST', `/drive/items/${workbookId}/workbook/closeSession`, {}, {}, '', { 'workbook-session-id': id }); + + if (Array.isArray(responseData)) { + returnData.push.apply(returnData, responseData as IDataObject[]); + } else if (responseData !== undefined) { + returnData.push(responseData as IDataObject); + } + } + //https://docs.microsoft.com/en-us/graph/api/table-list-columns?view=graph-rest-1.0&tabs=http + if (operation === 'getColumns') { + for (let i = 0; i < length; i++) { + qs = {}; + const workbookId = this.getNodeParameter('workbook', i) as string; + const worksheetId = this.getNodeParameter('worksheet', i) as string; + const tableId = this.getNodeParameter('table', i) as string; + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + const rawData = this.getNodeParameter('rawData', i) as boolean; + if (rawData) { + const filters = this.getNodeParameter('filters', i) as IDataObject; + if (filters.fields) { + qs['$select'] = filters.fields; + } + } + if (returnAll === true) { + responseData = await microsoftApiRequestAllItemsSkip.call(this, 'value', 'GET', `/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/tables/${tableId}/columns`, {}, qs); + } else { + qs['$top'] = this.getNodeParameter('limit', i) as number; + responseData = await microsoftApiRequest.call(this, 'GET', `/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/tables/${tableId}/columns`, {}, qs); + responseData = responseData.value; + } + if (!rawData) { + responseData = responseData.map((column: IDataObject) => ({ name: column.name })); + } else { + const dataProperty = this.getNodeParameter('dataProperty', i) as string; + responseData = { [dataProperty] : responseData }; + } + + if (Array.isArray(responseData)) { + returnData.push.apply(returnData, responseData as IDataObject[]); + } else if (responseData !== undefined) { + returnData.push(responseData as IDataObject); + } + } + } + //https://docs.microsoft.com/en-us/graph/api/table-list-rows?view=graph-rest-1.0&tabs=http + if (operation === 'getRows') { + for (let i = 0; i < length; i++) { + qs = {}; + const workbookId = this.getNodeParameter('workbook', i) as string; + const worksheetId = this.getNodeParameter('worksheet', i) as string; + const tableId = this.getNodeParameter('table', i) as string; + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + const rawData = this.getNodeParameter('rawData', i) as boolean; + if (rawData) { + const filters = this.getNodeParameter('filters', i) as IDataObject; + if (filters.fields) { + qs['$select'] = filters.fields; + } + } + if (returnAll === true) { + responseData = await microsoftApiRequestAllItemsSkip.call(this, 'value', 'GET', `/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/tables/${tableId}/rows`, {}, qs); + } else { + const rowsQs = { ...qs }; + rowsQs['$top'] = this.getNodeParameter('limit', i) as number; + responseData = await microsoftApiRequest.call(this, 'GET', `/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/tables/${tableId}/rows`, {}, rowsQs); + responseData = responseData.value; + } + if (!rawData) { + const columnsQs = { ...qs }; + columnsQs['$select'] = 'name'; + // TODO: That should probably be cached in the future + let columns = await microsoftApiRequestAllItemsSkip.call(this, 'value', 'GET', `/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/tables/${tableId}/columns`, {}, columnsQs); + //@ts-ignore + columns = columns.map(column => column.name); + for (let i = 0; i < responseData.length; i++) { + const object: IDataObject = {}; + for (let y = 0; y < columns.length; y++) { + object[columns[y]] = responseData[i].values[0][y]; + } + returnData.push({ ...object }); + } + } else { + const dataProperty = this.getNodeParameter('dataProperty', i) as string; + returnData.push({ [dataProperty]: responseData }); + } + } + } + if (operation === 'lookup') { + for (let i = 0; i < length; i++) { + qs = {}; + const workbookId = this.getNodeParameter('workbook', i) as string; + const worksheetId = this.getNodeParameter('worksheet', i) as string; + const tableId = this.getNodeParameter('table', i) as string; + const lookupColumn = this.getNodeParameter('lookupColumn', i) as string; + const lookupValue = this.getNodeParameter('lookupValue', i) as string; + const options = this.getNodeParameter('options', i) as IDataObject; + + responseData = await microsoftApiRequestAllItemsSkip.call(this, 'value', 'GET', `/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/tables/${tableId}/rows`, {}, {}); + + qs['$select'] = 'name'; + // TODO: That should probably be cached in the future + let columns = await microsoftApiRequestAllItemsSkip.call(this, 'value', 'GET', `/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/tables/${tableId}/columns`, {}, qs); + columns = columns.map((column: IDataObject) => column.name); + + if (!columns.includes(lookupColumn)) { + throw new Error(`Column ${lookupColumn} does not exist on the table selected`); + } + + result.length = 0; + for (let i = 0; i < responseData.length; i++) { + const object: IDataObject = {}; + for (let y = 0; y < columns.length; y++) { + object[columns[y]] = responseData[i].values[0][y]; + } + result.push({ ...object }); + } + + if (options.returnAllMatches) { + responseData = result.filter((data: IDataObject) => { + return (data[lookupColumn]?.toString() === lookupValue ); + }); + returnData.push.apply(returnData, responseData as IDataObject[]); + } else { + responseData = result.find((data: IDataObject) => { + return (data[lookupColumn]?.toString() === lookupValue ); + }); + returnData.push(responseData as IDataObject); + } + } + } + } + if (resource === 'workbook') { + for (let i = 0; i < length; i++) { + qs = {}; + //https://docs.microsoft.com/en-us/graph/api/worksheetcollection-add?view=graph-rest-1.0&tabs=http + if (operation === 'addWorksheet') { + const workbookId = this.getNodeParameter('workbook', i) as string; + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + const body: IDataObject = {}; + if (additionalFields.name) { + body.name = additionalFields.name; + } + const { id } = await microsoftApiRequest.call(this, 'POST', `/drive/items/${workbookId}/workbook/createSession`, { persistChanges: true }); + responseData = await microsoftApiRequest.call(this, 'POST', `/drive/items/${workbookId}/workbook/worksheets/add`, body, {}, '', { 'workbook-session-id': id }); + await microsoftApiRequest.call(this, 'POST', `/drive/items/${workbookId}/workbook/closeSession`, {}, {}, '', { 'workbook-session-id': id }); + } + if (operation === 'getAll') { + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + const filters = this.getNodeParameter('filters', i) as IDataObject; + if (filters.fields) { + qs['$select'] = filters.fields; + } + if (returnAll === true) { + responseData = await microsoftApiRequestAllItems.call(this, 'value', 'GET', `/drive/root/search(q='.xlsx')`, {}, qs); + } else { + qs['$top'] = this.getNodeParameter('limit', i) as number; + responseData = await microsoftApiRequest.call(this, 'GET', `/drive/root/search(q='.xlsx')`, {}, qs); + responseData = responseData.value; + } + } + + if (Array.isArray(responseData)) { + returnData.push.apply(returnData, responseData as IDataObject[]); + } else if (responseData !== undefined) { + returnData.push(responseData as IDataObject); + } + } + } + if (resource === 'worksheet') { + for (let i = 0; i < length; i++) { + qs = {}; + //https://docs.microsoft.com/en-us/graph/api/workbook-list-worksheets?view=graph-rest-1.0&tabs=http + if (operation === 'getAll') { + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + const workbookId = this.getNodeParameter('workbook', i) as string; + const filters = this.getNodeParameter('filters', i) as IDataObject; + if (filters.fields) { + qs['$select'] = filters.fields; + } + if (returnAll === true) { + responseData = await microsoftApiRequestAllItems.call(this, 'value', 'GET', `/drive/items/${workbookId}/workbook/worksheets`, {}, qs); + } else { + qs['$top'] = this.getNodeParameter('limit', i) as number; + responseData = await microsoftApiRequest.call(this, 'GET', `/drive/items/${workbookId}/workbook/worksheets`, {}, qs); + responseData = responseData.value; + } + } + //https://docs.microsoft.com/en-us/graph/api/worksheet-range?view=graph-rest-1.0&tabs=http + if (operation === 'getContent') { + const workbookId = this.getNodeParameter('workbook', i) as string; + const worksheetId = this.getNodeParameter('worksheet', i) as string; + const range = this.getNodeParameter('range', i) as string; + const rawData = this.getNodeParameter('rawData', i) as boolean; + if (rawData) { + const filters = this.getNodeParameter('filters', i) as IDataObject; + if (filters.fields) { + qs['$select'] = filters.fields; + } + } + + responseData = await microsoftApiRequest.call(this, 'GET', `/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/range(address='${range}')`, {}, qs); + + if (!rawData) { + const keyRow = this.getNodeParameter('keyRow', i) as number; + const dataStartRow = this.getNodeParameter('dataStartRow', i) as number; + if (responseData.values === null) { + throw new Error('Range did not return data'); + } + const keyValues = responseData.values[keyRow]; + for (let i = dataStartRow; i < responseData.values.length; i++) { + const object: IDataObject = {}; + for (let y = 0; y < keyValues.length; y++) { + object[keyValues[y]] = responseData.values[i][y]; + } + returnData.push({ ...object }); + } + } else { + const dataProperty = this.getNodeParameter('dataProperty', i) as string; + returnData.push({ [dataProperty]: responseData }); + } + } + } + } + + return [this.helpers.returnJsonArray(returnData)]; + } +} diff --git a/packages/nodes-base/nodes/Microsoft/Excel/TableDescription.ts b/packages/nodes-base/nodes/Microsoft/Excel/TableDescription.ts new file mode 100644 index 0000000000..8c399330d5 --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/Excel/TableDescription.ts @@ -0,0 +1,625 @@ +import { INodeProperties } from 'n8n-workflow'; + +export const tableOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'table', + ], + }, + }, + options: [ + { + name: 'Add Row', + value: 'addRow', + description: 'Adds rows to the end of the table' + }, + { + name: 'Get Columns', + value: 'getColumns', + description: 'Retrieve a list of tablecolumns', + }, + { + name: 'Get Rows', + value: 'getRows', + description: 'Retrieve a list of tablerows', + }, + { + name: 'Lookup', + value: 'lookup', + description: 'Looks for a specific column value and then returns the matching row' + }, + ], + default: 'addRow', + description: 'The operation to perform.', + }, +] as INodeProperties[]; + +export const tableFields = [ + +/* -------------------------------------------------------------------------- */ +/* table:addRow */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Workbook', + name: 'workbook', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getWorkbooks', + }, + displayOptions: { + show: { + operation: [ + 'addRow', + ], + resource: [ + 'table', + ], + }, + }, + default: '', + }, + { + displayName: 'Worksheet', + name: 'worksheet', + type: 'options', + required: true, + typeOptions: { + loadOptionsMethod: 'getworksheets', + loadOptionsDependsOn: [ + 'workbook', + ], + }, + displayOptions: { + show: { + operation: [ + 'addRow', + ], + resource: [ + 'table', + ], + }, + }, + default: '', + }, + { + displayName: 'Table', + name: 'table', + type: 'options', + required: true, + typeOptions: { + loadOptionsMethod: 'getTables', + loadOptionsDependsOn: [ + 'worksheet', + ], + }, + displayOptions: { + show: { + operation: [ + 'addRow', + ], + resource: [ + 'table', + ], + }, + }, + default: '', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + operation: [ + 'addRow', + ], + resource: [ + 'table', + ], + }, + }, + options: [ + { + displayName: 'Index', + name: 'index', + type: 'number', + default: 0, + typeOptions: { + minValue: 0, + }, + description: `Specifies the relative position of the new row. If not defined,
+ the addition happens at the end. Any rows below the inserted row are shifted downwards. Zero-indexed`, + }, + ], + }, +/* -------------------------------------------------------------------------- */ +/* table:getRows */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Workbook', + name: 'workbook', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getWorkbooks', + }, + displayOptions: { + show: { + operation: [ + 'getRows', + ], + resource: [ + 'table', + ], + }, + }, + default: '', + }, + { + displayName: 'Worksheet', + name: 'worksheet', + type: 'options', + required: true, + typeOptions: { + loadOptionsMethod: 'getworksheets', + loadOptionsDependsOn: [ + 'workbook', + ], + }, + displayOptions: { + show: { + operation: [ + 'getRows', + ], + resource: [ + 'table', + ], + }, + }, + default: '', + }, + { + displayName: 'Table', + name: 'table', + type: 'options', + required: true, + typeOptions: { + loadOptionsMethod: 'getTables', + loadOptionsDependsOn: [ + 'worksheet', + ], + }, + displayOptions: { + show: { + operation: [ + 'getRows', + ], + resource: [ + 'table', + ], + }, + }, + default: '', + }, + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + operation: [ + 'getRows', + ], + resource: [ + 'table', + ], + }, + }, + default: false, + description: 'If all results should be returned or only up to a given limit.', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + displayOptions: { + show: { + operation: [ + 'getRows', + ], + resource: [ + 'table', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 500, + }, + default: 100, + description: 'How many results to return.', + }, + { + displayName: 'RAW Data', + name: 'rawData', + type: 'boolean', + displayOptions: { + show: { + operation: [ + 'getRows', + ], + resource: [ + 'table', + ], + }, + }, + default: false, + description: 'If the data should be returned RAW instead of parsed into keys according to their header.', + }, + { + displayName: 'Data Property', + name: 'dataProperty', + type: 'string', + default: 'data', + displayOptions: { + show: { + operation: [ + 'getRows' + ], + resource: [ + 'table', + ], + rawData: [ + true, + ], + }, + }, + description: 'The name of the property into which to write the RAW data.', + }, + { + displayName: 'Filters', + name: 'filters', + type: 'collection', + placeholder: 'Add Filter', + default: {}, + displayOptions: { + show: { + operation: [ + 'getRows', + ], + resource: [ + 'table', + ], + rawData: [ + true, + ], + }, + }, + options: [ + { + displayName: 'Fields', + name: 'fields', + type: 'string', + default: '', + description: `Fields the response will containt. Multiple can be added separated by ,.`, + }, + ] + }, +/* -------------------------------------------------------------------------- */ +/* table:getColumns */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Workbook', + name: 'workbook', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getWorkbooks', + }, + displayOptions: { + show: { + operation: [ + 'getColumns', + ], + resource: [ + 'table', + ], + }, + }, + default: '', + }, + { + displayName: 'Worksheet', + name: 'worksheet', + type: 'options', + required: true, + typeOptions: { + loadOptionsMethod: 'getworksheets', + loadOptionsDependsOn: [ + 'workbook', + ], + }, + displayOptions: { + show: { + operation: [ + 'getColumns', + ], + resource: [ + 'table', + ], + }, + }, + default: '', + }, + { + displayName: 'Table', + name: 'table', + type: 'options', + required: true, + typeOptions: { + loadOptionsMethod: 'getTables', + loadOptionsDependsOn: [ + 'worksheet', + ], + }, + displayOptions: { + show: { + operation: [ + 'getColumns', + ], + resource: [ + 'table', + ], + }, + }, + default: '', + }, + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + operation: [ + 'getColumns', + ], + resource: [ + 'table', + ], + }, + }, + default: false, + description: 'If all results should be returned or only up to a given limit.', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + displayOptions: { + show: { + operation: [ + 'getColumns', + ], + resource: [ + 'table', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 500, + }, + default: 100, + description: 'How many results to return.', + }, + { + displayName: 'RAW Data', + name: 'rawData', + type: 'boolean', + displayOptions: { + show: { + operation: [ + 'getColumns', + ], + resource: [ + 'table', + ], + }, + }, + default: false, + description: 'If the data should be returned RAW instead of parsed into keys according to their header.', + }, + { + displayName: 'Data Property', + name: 'dataProperty', + type: 'string', + default: 'data', + displayOptions: { + show: { + operation: [ + 'getColumns' + ], + resource: [ + 'table', + ], + rawData: [ + true, + ], + }, + }, + description: 'The name of the property into which to write the RAW data.', + }, + { + displayName: 'Filters', + name: 'filters', + type: 'collection', + placeholder: 'Add Filter', + default: {}, + displayOptions: { + show: { + operation: [ + 'getColumns', + ], + resource: [ + 'table', + ], + rawData: [ + true + ], + }, + }, + options: [ + { + displayName: 'Fields', + name: 'fields', + type: 'string', + default: '', + description: `Fields the response will containt. Multiple can be added separated by ,.`, + }, + ] + }, +/* -------------------------------------------------------------------------- */ +/* table:lookup */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Workbook', + name: 'workbook', + type: 'options', + required: true, + typeOptions: { + loadOptionsMethod: 'getWorkbooks', + }, + displayOptions: { + show: { + operation: [ + 'lookup', + ], + resource: [ + 'table', + ], + }, + }, + default: '', + }, + { + displayName: 'Worksheet', + name: 'worksheet', + type: 'options', + required: true, + typeOptions: { + loadOptionsMethod: 'getworksheets', + loadOptionsDependsOn: [ + 'workbook', + ], + }, + displayOptions: { + show: { + operation: [ + 'lookup', + ], + resource: [ + 'table', + ], + }, + }, + default: '', + }, + { + displayName: 'Table', + name: 'table', + type: 'options', + required: true, + typeOptions: { + loadOptionsMethod: 'getTables', + loadOptionsDependsOn: [ + 'worksheet', + ], + }, + displayOptions: { + show: { + operation: [ + 'lookup', + ], + resource: [ + 'table', + ], + }, + }, + default: '', + }, + { + displayName: 'Lookup Column', + name: 'lookupColumn', + type: 'string', + default: '', + placeholder: 'Email', + required: true, + displayOptions: { + show: { + resource: [ + 'table', + ], + operation: [ + 'lookup' + ], + }, + }, + description: 'The name of the column in which to look for value.', + }, + { + displayName: 'Lookup Value', + name: 'lookupValue', + type: 'string', + default: '', + placeholder: 'frank@example.com', + required: true, + displayOptions: { + show: { + resource: [ + 'table', + ], + operation: [ + 'lookup' + ], + }, + }, + description: 'The value to look for in column.', + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + displayOptions: { + show: { + resource: [ + 'table', + ], + operation: [ + 'lookup', + ], + }, + }, + options: [ + { + displayName: 'Return All Matches', + name: 'returnAllMatches', + type: 'boolean', + default: false, + description: 'By default only the first result gets returned. If options gets set all found matches get returned.', + }, + ], + } +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Microsoft/Excel/WorkbookDescription.ts b/packages/nodes-base/nodes/Microsoft/Excel/WorkbookDescription.ts new file mode 100644 index 0000000000..61f7b6f501 --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/Excel/WorkbookDescription.ts @@ -0,0 +1,154 @@ +import { INodeProperties } from 'n8n-workflow'; + +export const workbookOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'workbook', + ], + }, + }, + options: [ + { + name: 'Add Worksheet', + value: 'addWorksheet', + description: 'Adds a new worksheet to the workbook.', + }, + { + name: 'Get All', + value: 'getAll', + description: 'Get data of all workbooks', + }, + ], + default: 'create', + description: 'The operation to perform.', + }, +] as INodeProperties[]; + +export const workbookFields = [ + +/* -------------------------------------------------------------------------- */ +/* workbook:addWorksheet */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Workbook', + name: 'workbook', + type: 'options', + required: true, + typeOptions: { + loadOptionsMethod: 'getWorkbooks', + }, + displayOptions: { + show: { + operation: [ + 'addWorksheet', + ], + resource: [ + 'workbook', + ], + }, + }, + default: '', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + operation: [ + 'addWorksheet', + ], + resource: [ + 'workbook', + ], + }, + }, + options: [ + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + description: `The name of the worksheet to be added. If specified, name should be unqiue.
+ If not specified, Excel determines the name of the new worksheet.`, + }, + ] + }, +/* -------------------------------------------------------------------------- */ +/* workbook:getAll */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'workbook', + ], + }, + }, + default: false, + description: 'If all results should be returned or only up to a given limit.', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'workbook', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 500, + }, + default: 100, + description: 'How many results to return.', + }, + { + displayName: 'Filters', + name: 'filters', + type: 'collection', + placeholder: 'Add Filter', + default: {}, + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'workbook', + ], + }, + }, + options: [ + { + displayName: 'Fields', + name: 'fields', + type: 'string', + default: '', + description: `Fields the response will containt. Multiple can be added separated by ,.`, + }, + ] + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Microsoft/Excel/WorksheetDescription.ts b/packages/nodes-base/nodes/Microsoft/Excel/WorksheetDescription.ts new file mode 100644 index 0000000000..e5811e6c68 --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/Excel/WorksheetDescription.ts @@ -0,0 +1,303 @@ +import { INodeProperties } from 'n8n-workflow'; + +export const worksheetOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'worksheet', + ], + }, + }, + options: [ + { + name: 'Get All', + value: 'getAll', + description: 'Get all worksheets', + }, + { + name: 'Get Content', + value: 'getContent', + description: 'Get worksheet content', + }, + ], + default: 'create', + description: 'The operation to perform.', + }, +] as INodeProperties[]; + +export const worksheetFields = [ + +/* -------------------------------------------------------------------------- */ +/* worksheet:getAll */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Workbook', + name: 'workbook', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getWorkbooks', + }, + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'worksheet', + ], + }, + }, + default: '', + }, + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'worksheet', + ], + }, + }, + default: false, + description: 'If all results should be returned or only up to a given limit.', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'worksheet', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 500, + }, + default: 100, + description: 'How many results to return.', + }, + { + displayName: 'Filters', + name: 'filters', + type: 'collection', + placeholder: 'Add Filter', + default: {}, + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'worksheet', + ], + }, + }, + options: [ + { + displayName: 'Fields', + name: 'fields', + type: 'string', + default: '', + description: `Fields the response will containt. Multiple can be added separated by ,.`, + }, + ] + }, +/* -------------------------------------------------------------------------- */ +/* worksheet:getContent */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Workbook', + name: 'workbook', + type: 'options', + required: true, + typeOptions: { + loadOptionsMethod: 'getWorkbooks', + }, + displayOptions: { + show: { + operation: [ + 'getContent', + ], + resource: [ + 'worksheet', + ], + }, + }, + default: '', + }, + { + displayName: 'Worksheet', + name: 'worksheet', + type: 'options', + required: true, + typeOptions: { + loadOptionsMethod: 'getworksheets', + loadOptionsDependsOn: [ + 'workbook', + ], + }, + displayOptions: { + show: { + operation: [ + 'getContent', + ], + resource: [ + 'worksheet', + ], + }, + }, + default: '', + }, + { + displayName: 'Range', + name: 'range', + type: 'string', + displayOptions: { + show: { + operation: [ + 'getContent', + ], + resource: [ + 'worksheet', + ], + }, + }, + default: 'A1:C3', + required: true, + description: 'The address or the name of the range. If not specified, the entire worksheet range is returned.', + }, + { + displayName: 'RAW Data', + name: 'rawData', + type: 'boolean', + displayOptions: { + show: { + operation: [ + 'getContent', + ], + resource: [ + 'worksheet', + ], + }, + }, + default: false, + description: 'If the data should be returned RAW instead of parsed into keys according to their header.', + }, + { + displayName: 'Data Property', + name: 'dataProperty', + type: 'string', + default: 'data', + displayOptions: { + show: { + operation: [ + 'getContent' + ], + resource: [ + 'worksheet', + ], + rawData: [ + true, + ], + }, + }, + description: 'The name of the property into which to write the RAW data.', + }, + { + displayName: 'Data Start Row', + name: 'dataStartRow', + type: 'number', + typeOptions: { + minValue: 1, + }, + default: 1, + displayOptions: { + show: { + operation: [ + 'getContent', + ], + resource: [ + 'worksheet', + ], + }, + hide: { + rawData: [ + true + ], + }, + }, + description: 'Index of the first row which contains
the actual data and not the keys. Starts with 0.', + }, + { + displayName: 'Key Row', + name: 'keyRow', + type: 'number', + typeOptions: { + minValue: 0, + }, + displayOptions: { + show: { + operation: [ + 'getContent', + ], + resource: [ + 'worksheet', + ], + }, + hide: { + rawData: [ + true + ], + }, + }, + default: 0, + description: 'Index of the row which contains the keys. Starts at 0.
The incoming node data is matched to the keys for assignment. The matching is case sensitve.', + }, + { + displayName: 'Filters', + name: 'filters', + type: 'collection', + placeholder: 'Add Filter', + default: {}, + displayOptions: { + show: { + operation: [ + 'getContent', + ], + resource: [ + 'worksheet', + ], + rawData: [ + true, + ], + }, + }, + options: [ + { + displayName: 'Fields', + name: 'fields', + type: 'string', + default: '', + description: `Fields the response will containt. Multiple can be added separated by ,.`, + }, + ] + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Microsoft/Excel/excel.png b/packages/nodes-base/nodes/Microsoft/Excel/excel.png new file mode 100644 index 0000000000..ba2b10c063 Binary files /dev/null and b/packages/nodes-base/nodes/Microsoft/Excel/excel.png differ diff --git a/packages/nodes-base/nodes/Microsoft/OneDrive/FileDescription.ts b/packages/nodes-base/nodes/Microsoft/OneDrive/FileDescription.ts new file mode 100644 index 0000000000..0e9bc26cba --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/OneDrive/FileDescription.ts @@ -0,0 +1,377 @@ +import { INodeProperties } from 'n8n-workflow'; + +export const fileOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'file', + ], + }, + }, + options: [ + { + name: 'Copy', + value: 'copy', + description: 'Copy a file', + }, + { + name: 'Delete', + value: 'delete', + description: 'Delete a file', + }, + { + name: 'Download', + value: 'download', + description: 'Download a file', + }, + { + name: 'Get', + value: 'get', + description: 'Get a file', + }, + { + name: 'Search', + value: 'search', + description: 'Search a file', + }, + { + name: 'Upload', + value: 'upload', + description: 'Upload a file up to 4MB in size', + }, + ], + default: 'upload', + description: 'The operation to perform.', + }, +] as INodeProperties[]; + +export const fileFields = [ + +/* -------------------------------------------------------------------------- */ +/* file:copy */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'File ID', + name: 'fileId', + type: 'string', + displayOptions: { + show: { + operation: [ + 'copy', + ], + resource: [ + 'file', + ], + }, + }, + default: '', + description: 'File ID', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + displayOptions: { + show: { + operation: [ + 'copy', + ], + resource: [ + 'file', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + description: `The new name for the copy. If this isn't provided, the same name will be used as the original.`, + }, + ], + }, + { + displayName: 'Parent Reference', + name: 'parentReference', + type: 'collection', + placeholder: 'Add Parent Reference', + description: 'Reference to the parent item the copy will be created in Details ', + displayOptions: { + show: { + operation: [ + 'copy', + ], + resource: [ + 'file', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Drive ID', + name: 'driveId', + type: 'string', + default: '', + description: 'Identifier of the drive instance that contains the item.', + }, + { + displayName: 'Drive Type', + name: 'driveType', + type: 'string', + default: '', + description: 'Identifies the type of drive.', + }, + { + displayName: 'ID', + name: 'id', + type: 'string', + default: '', + description: 'Identifier of the item in the drive.', + }, + { + displayName: 'List ID', + name: 'listId', + type: 'string', + default: '', + description: 'Identifier of the list.', + }, + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + description: 'The name of the item being referenced', + }, + { + displayName: 'Path', + name: 'path', + type: 'string', + default: '', + description: 'Path that can be used to navigate to the item', + }, + { + displayName: 'Share ID', + name: 'shareId', + type: 'string', + default: '', + description: 'Identifier for a shared resource that can be accessed via the Shares API.', + }, + { + displayName: 'Site ID', + name: 'siteId', + type: 'string', + default: '', + description: 'Identifier of the site.', + }, + ], + }, +/* -------------------------------------------------------------------------- */ +/* file:delete */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'File ID', + name: 'fileId', + type: 'string', + displayOptions: { + show: { + operation: [ + 'delete', + ], + resource: [ + 'file', + ], + }, + }, + default: '', + description: 'Field ID', + }, +/* -------------------------------------------------------------------------- */ +/* file:download */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'File ID', + name: 'fileId', + type: 'string', + displayOptions: { + show: { + operation: [ + 'download', + ], + resource: [ + 'file', + ], + }, + }, + default: '', + description: 'File ID', + }, + { + 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.', + }, +/* -------------------------------------------------------------------------- */ +/* file:get */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'File ID', + name: 'fileId', + type: 'string', + displayOptions: { + show: { + operation: [ + 'get', + ], + resource: [ + 'file', + ], + }, + }, + default: '', + description: 'Field ID', + }, +/* -------------------------------------------------------------------------- */ +/* file:search */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Query', + name: 'query', + type: 'string', + displayOptions: { + show: { + operation: [ + 'search', + ], + resource: [ + 'file', + ], + }, + }, + default: '', + description: `The query text used to search for items. Values may be matched + across several fields including filename, metadata, and file content.`, + }, +/* -------------------------------------------------------------------------- */ +/* file:upload */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'File Name', + name: 'fileName', + type: 'string', + displayOptions: { + show: { + operation: [ + 'upload', + ], + resource: [ + 'file', + ], + }, + }, + default: '', + description: 'The name the file should be saved as.', + }, + { + displayName: 'Parent ID', + name: 'parentId', + required: true, + type: 'string', + displayOptions: { + show: { + operation: [ + 'upload', + ], + resource: [ + 'file', + ], + }, + }, + default: '', + description: 'ID of the parent folder that will contain the file.', + }, + { + displayName: 'Binary Data', + name: 'binaryData', + type: 'boolean', + default: false, + required: true, + displayOptions: { + show: { + operation: [ + 'upload', + ], + resource: [ + 'file', + ], + }, + }, + description: 'If the data to upload should be taken from binary field.', + }, + { + displayName: 'File Content', + name: 'fileContent', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + binaryData: [ + false, + ], + operation: [ + 'upload', + ], + resource: [ + 'file', + ], + }, + + }, + placeholder: '', + description: 'The text content of the file.', + }, + { + displayName: 'Binary Property', + name: 'binaryPropertyName', + type: 'string', + default: 'data', + required: true, + displayOptions: { + show: { + binaryData: [ + true, + ], + operation: [ + 'upload', + ], + resource: [ + 'file', + ], + }, + + }, + placeholder: '', + description: 'Name of the binary property which contains
the data for the file.', + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Microsoft/OneDrive/FolderDescription.ts b/packages/nodes-base/nodes/Microsoft/OneDrive/FolderDescription.ts new file mode 100644 index 0000000000..62f488ae0c --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/OneDrive/FolderDescription.ts @@ -0,0 +1,101 @@ +import { INodeProperties } from 'n8n-workflow'; + +export const folderOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'folder', + ], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create a folder', + }, + { + name: 'Get Children', + value: 'getChildren', + description: 'Get items inside a folder', + }, + { + name: 'Search', + value: 'search', + description: 'Search a folder', + }, + ], + default: 'getChildren', + description: 'The operation to perform.', + }, +] as INodeProperties[]; + +export const folderFields = [ + +/* -------------------------------------------------------------------------- */ +/* folder:create */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Name', + name: 'name', + required: true, + type: 'string', + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'folder', + ], + }, + }, + default: '', + description: `Folder's name`, + }, +/* -------------------------------------------------------------------------- */ +/* folder:getChildren */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Folder ID', + name: 'folderId', + type: 'string', + displayOptions: { + show: { + operation: [ + 'getChildren', + ], + resource: [ + 'folder', + ], + }, + }, + default: '', + description: 'Folder ID', + }, +/* -------------------------------------------------------------------------- */ +/* folder:search */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Query', + name: 'query', + type: 'string', + displayOptions: { + show: { + operation: [ + 'search', + ], + resource: [ + 'folder', + ], + }, + }, + default: '', + description: `The query text used to search for items. Values may be matched + across several fields including filename, metadata, and file content.`, + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Microsoft/OneDrive/GenericFunctions.ts b/packages/nodes-base/nodes/Microsoft/OneDrive/GenericFunctions.ts new file mode 100644 index 0000000000..1bd1bee202 --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/OneDrive/GenericFunctions.ts @@ -0,0 +1,84 @@ +import { + OptionsWithUri + } from 'request'; + +import { + IExecuteFunctions, + IExecuteSingleFunctions, + ILoadOptionsFunctions, +} from 'n8n-core'; + +import { + IDataObject +} from 'n8n-workflow'; + +export async function microsoftApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, headers: IDataObject = {}, option: IDataObject = { json: true }): Promise { // tslint:disable-line:no-any + const options: OptionsWithUri = { + headers: { + 'Content-Type': 'application/json', + }, + method, + body, + qs, + uri: uri || `https://graph.microsoft.com/v1.0/me${resource}`, + }; + try { + Object.assign(options, option); + if (Object.keys(headers).length !== 0) { + options.headers = Object.assign({}, options.headers, headers); + } + if (Object.keys(qs).length === 0) { + delete options.qs; + } + if (Object.keys(body).length === 0) { + delete options.body; + } + + //@ts-ignore + return await this.helpers.requestOAuth.call(this, 'microsoftOneDriveOAuth2Api', options); + } catch (error) { + if (error.response && error.response.body && error.response.body.error && error.response.body.error.message) { + // Try to return the error prettier + throw new Error(`Microsoft OneDrive response [${error.statusCode}]: ${error.response.body.error.message}`); + } + throw error; + } +} + +export async function microsoftApiRequestAllItems(this: IExecuteFunctions | ILoadOptionsFunctions, propertyName: string ,method: string, endpoint: string, body: any = {}, query: IDataObject = {}): Promise { // tslint:disable-line:no-any + + const returnData: IDataObject[] = []; + + let responseData; + let uri: string | undefined; + query['$top'] = 100; + + do { + responseData = await microsoftApiRequest.call(this, method, endpoint, body, query, uri); + uri = responseData['@odata.nextLink']; + returnData.push.apply(returnData, responseData[propertyName]); + } while ( + responseData['@odata.nextLink'] !== undefined + ); + + return returnData; +} + +export async function microsoftApiRequestAllItemsSkip(this: IExecuteFunctions | ILoadOptionsFunctions, propertyName: string ,method: string, endpoint: string, body: any = {}, query: IDataObject = {}): Promise { // tslint:disable-line:no-any + + const returnData: IDataObject[] = []; + + let responseData; + query['$top'] = 100; + query['$skip'] = 0; + + do { + responseData = await microsoftApiRequest.call(this, method, endpoint, body, query); + query['$skip'] += query['$top']; + returnData.push.apply(returnData, responseData[propertyName]); + } while ( + responseData['value'].length !== 0 + ); + + return returnData; +} diff --git a/packages/nodes-base/nodes/Microsoft/OneDrive/MicrosoftOneDrive.node.ts b/packages/nodes-base/nodes/Microsoft/OneDrive/MicrosoftOneDrive.node.ts new file mode 100644 index 0000000000..2af523cbc0 --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/OneDrive/MicrosoftOneDrive.node.ts @@ -0,0 +1,228 @@ +import { + BINARY_ENCODING, + IExecuteFunctions, +} from 'n8n-core'; + +import { + IBinaryKeyData, + IDataObject, + INodeExecutionData, + INodeType, + INodeTypeDescription, +} from 'n8n-workflow'; + +import { + microsoftApiRequest, + microsoftApiRequestAllItems, +} from './GenericFunctions'; + +import { + fileFields, + fileOperations, +} from './FileDescription'; + +import { + folderFields, + folderOperations, +} from './FolderDescription'; + +export class MicrosoftOneDrive implements INodeType { + description: INodeTypeDescription = { + displayName: 'Microsoft OneDrive', + name: 'microsoftOneDrive', + icon: 'file:oneDrive.png', + group: ['input'], + version: 1, + subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', + description: 'Consume Microsoft OneDrive API.', + defaults: { + name: 'Microsoft OneDrive', + color: '#1d4bab', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'microsoftOneDriveOAuth2Api', + required: true, + }, + ], + properties: [ + { + displayName: 'Resource', + name: 'resource', + type: 'options', + options: [ + { + name: 'File', + value: 'file', + }, + { + name: 'Folder', + value: 'folder', + }, + ], + default: 'file', + description: 'The resource to operate on.', + }, + ...fileOperations, + ...fileFields, + ...folderOperations, + ...folderFields, + ], + }; + + async execute(this: IExecuteFunctions): Promise { + const items = this.getInputData(); + const returnData: IDataObject[] = []; + const length = items.length as unknown as number; + const qs: IDataObject = {}; + let responseData; + const resource = this.getNodeParameter('resource', 0) as string; + const operation = this.getNodeParameter('operation', 0) as string; + for (let i = 0; i < length; i++) { + if (resource === 'file') { + //https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_copy?view=odsp-graph-online + if (operation === 'copy') { + const fileId = this.getNodeParameter('fileId', i) as string; + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + const parentReference = this.getNodeParameter('parentReference', i) as IDataObject; + const body: IDataObject = {}; + if (parentReference) { + body.parentReference = { ...parentReference }; + } + if (additionalFields.name) { + body.name = additionalFields.name as string; + } + responseData = await microsoftApiRequest.call(this, 'POST', `/drive/items/${fileId}/copy`, body, {}, undefined, {}, { json: true, resolveWithFullResponse: true }); + responseData = { location : responseData.headers.location }; + returnData.push(responseData as IDataObject); + } + //https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_delete?view=odsp-graph-online + if (operation === 'delete') { + const fileId = this.getNodeParameter('fileId', i) as string; + responseData = await microsoftApiRequest.call(this, 'DELETE', `/drive/items/${fileId}`); + responseData = { success: true }; + returnData.push(responseData as IDataObject); + } + //https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_list_children?view=odsp-graph-online + if (operation === 'download') { + const fileId = this.getNodeParameter('fileId', i) as string; + const dataPropertyNameDownload = this.getNodeParameter('binaryPropertyName', i) as string; + responseData = await microsoftApiRequest.call(this, 'GET', `/drive/items/${fileId}`); + + const fileName = responseData.name; + + if (responseData.file === undefined) { + throw new Error('The ID you provided does not belong to a file.'); + } + + let mimeType: string | undefined; + if (responseData.file.mimeType) { + mimeType = responseData.file.mimeType; + } + + responseData = await microsoftApiRequest.call(this, 'GET', `/drive/items/${fileId}/content`, {}, {}, undefined, {}, { encoding: null, resolveWithFullResponse: true }); + + const newItem: INodeExecutionData = { + json: items[i].json, + binary: {}, + }; + + if (mimeType === undefined && responseData.headers['content-type']) { + mimeType = responseData.headers['content-type']; + } + + 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, items[i].binary); + } + + items[i] = newItem; + + const data = Buffer.from(responseData.body); + + items[i].binary![dataPropertyNameDownload] = await this.helpers.prepareBinaryData(data as unknown as Buffer, fileName, mimeType); + } + //https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_get?view=odsp-graph-online + if (operation === 'get') { + const fileId = this.getNodeParameter('fileId', i) as string; + responseData = await microsoftApiRequest.call(this, 'GET', `/drive/items/${fileId}`); + returnData.push(responseData as IDataObject); + } + //https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_search?view=odsp-graph-online + if (operation === 'search') { + const query = this.getNodeParameter('query', i) as string; + responseData = await microsoftApiRequestAllItems.call(this, 'value', 'GET', `/drive/root/search(q='${query}')`); + responseData = responseData.filter((item: IDataObject) => item.file); + returnData.push.apply(returnData, responseData as IDataObject[]); + } + //https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_put_content?view=odsp-graph-online#example-upload-a-new-file + if (operation === 'upload') { + const parentId = this.getNodeParameter('parentId', i) as string; + const isBinaryData = this.getNodeParameter('binaryData', i) as boolean; + const fileName = this.getNodeParameter('fileName', i) as string; + + if (isBinaryData) { + const binaryPropertyName = this.getNodeParameter('binaryPropertyName', 0) as string; + + if (items[i].binary === undefined) { + throw new Error('No binary data exists on item!'); + } + //@ts-ignore + if (items[i].binary[binaryPropertyName] === undefined) { + throw new Error(`No binary data property "${binaryPropertyName}" does not exists on item!`); + } + + const binaryData = (items[i].binary as IBinaryKeyData)[binaryPropertyName]; + + const body = Buffer.from(binaryData.data, BINARY_ENCODING); + responseData = await microsoftApiRequest.call(this, 'PUT', `/drive/items/${parentId}:/${fileName || binaryData.fileName}:/content`, body, {}, undefined, { 'Content-Type': binaryData.mimeType, 'Content-length': body.length }, {} ); + + returnData.push(JSON.parse(responseData) as IDataObject); + } else { + const body = this.getNodeParameter('fileContent', i) as string; + if (fileName === '') { + throw new Error('File name must be set!'); + } + responseData = await microsoftApiRequest.call(this, 'PUT', `/drive/items/${parentId}:/${fileName}:/content`, body , {}, undefined, { 'Content-Type': 'text/plain' } ); + returnData.push(responseData as IDataObject); + } + } + } + if (resource === 'folder') { + //https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_post_children?view=odsp-graph-online + if (operation === 'create') { + const name = this.getNodeParameter('name', i) as string; + const body: IDataObject = { + name, + folder: {}, + }; + responseData = await microsoftApiRequest.call(this, 'POST', '/drive/root/children', body); + returnData.push(responseData); + } + //https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_list_children?view=odsp-graph-online + if (operation === 'getChildren') { + const folderId = this.getNodeParameter('folderId', i) as string; + responseData = await microsoftApiRequestAllItems.call(this, 'value', 'GET', `/drive/items/${folderId}/children`); + returnData.push.apply(returnData, responseData as IDataObject[]); + } + //https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_search?view=odsp-graph-online + if (operation === 'search') { + const query = this.getNodeParameter('query', i) as string; + responseData = await microsoftApiRequestAllItems.call(this, 'value', 'GET', `/drive/root/search(q='${query}')`); + responseData = responseData.filter((item: IDataObject) => item.folder); + returnData.push.apply(returnData, responseData as IDataObject[]); + } + } + } + if (resource === 'file' && operation === 'download') { + // For file downloads the files get attached to the existing items + return this.prepareOutputData(items); + } else { + return [this.helpers.returnJsonArray(returnData)]; + } + } +} diff --git a/packages/nodes-base/nodes/Microsoft/OneDrive/oneDrive.png b/packages/nodes-base/nodes/Microsoft/OneDrive/oneDrive.png new file mode 100644 index 0000000000..44f3f594ff Binary files /dev/null and b/packages/nodes-base/nodes/Microsoft/OneDrive/oneDrive.png differ diff --git a/packages/nodes-base/nodes/Zoho/GenericFunctions.ts b/packages/nodes-base/nodes/Zoho/GenericFunctions.ts index 04fe204858..4deea5f6f5 100644 --- a/packages/nodes-base/nodes/Zoho/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Zoho/GenericFunctions.ts @@ -41,14 +41,17 @@ export async function zohoApiRequestAllItems(this: IExecuteFunctions | ILoadOpti let responseData; let uri: string | undefined; + query.per_page = 200; + query.page = 0; do { responseData = await zohoApiRequest.call(this, method, endpoint, body, query, uri); - uri = responseData.nextRecordsUrl; + uri = responseData.info.more_records; returnData.push.apply(returnData, responseData[propertyName]); + query.page++; } while ( - responseData.nextRecordsUrl !== undefined && - responseData.nextRecordsUrl !== null + responseData.info.more_records !== undefined && + responseData.info.more_records === true ); return returnData; diff --git a/packages/nodes-base/nodes/Zoho/LeadDescription.ts b/packages/nodes-base/nodes/Zoho/LeadDescription.ts index 198abbe472..3b7777befd 100644 --- a/packages/nodes-base/nodes/Zoho/LeadDescription.ts +++ b/packages/nodes-base/nodes/Zoho/LeadDescription.ts @@ -1,4 +1,4 @@ -import { INodeProperties } from "n8n-workflow"; +import { INodeProperties } from 'n8n-workflow'; export const leadOperations = [ { @@ -18,6 +18,11 @@ export const leadOperations = [ value: 'create', description: 'Create a new lead', }, + { + name: 'Delete', + value: 'delete', + description: 'Delete a lead', + }, { name: 'Get', value: 'get', @@ -29,15 +34,15 @@ export const leadOperations = [ description: 'Get data of all leads', }, { - name: 'Update', - value: 'update', - description: 'Update new lead', + name: 'Get Fields', + value: 'getFields', + description: `Get the fields' metadata`, }, { - name: 'Delete', - value: 'delete', - description: 'Delete a lead', - } + name: 'Update', + value: 'update', + description: 'Update a lead', + }, ], default: 'create', description: 'The operation to perform.', @@ -49,7 +54,6 @@ export const leadFields = [ /* -------------------------------------------------------------------------- */ /* lead:create */ /* -------------------------------------------------------------------------- */ - { displayName: 'Last Name', name: 'lastName', @@ -86,86 +90,617 @@ export const leadFields = [ }, options: [ { - displayName: 'Avatar', - name: 'avatar', - type: 'string', - default: '', - description: 'An avatar image URL. note: the image url needs to be https.', + displayName: 'Annual Revenue', + name: 'annualRevenue', + type: 'number', + typeOptions: { + numberPrecision: 2, + }, + default: 0, }, { - displayName: 'Name', - name: 'name', + displayName: 'Company', + name: 'company', type: 'string', default: '', - description: 'Name of the user', + }, + { + displayName: 'Description', + name: 'description', + type: 'string', + default: '', + }, + { + displayName: 'Email', + name: 'email', + type: 'string', + default: '', + }, + { + displayName: 'Email Opt Out', + name: 'emailOptOut', + type: 'boolean', + default: false, + }, + { + displayName: 'Fax', + name: 'fax', + type: 'string', + default: '', + }, + { + displayName: 'First Name', + name: 'firstName', + type: 'string', + default: '', + }, + { + displayName: 'Industry', + name: 'industry', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getIndustries', + }, + default: '', + }, + { + displayName: 'Is Record Duplicate', + name: 'isRecordDuplicate', + type: 'boolean', + default: false, + }, + { + displayName: 'Lead Source', + name: 'leadSource', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getLeadSources', + }, + default: '', + }, + { + displayName: 'Lead Status', + name: 'leadStatus', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getLeadStatuses' + }, + default: '', + }, + { + displayName: 'Mobile', + name: 'mobile', + type: 'string', + default: '', + }, + { + displayName: 'No. of Employees', + name: 'numberOfEmployees', + type: 'number', + default: 1, + }, + { + displayName: 'Owner', + name: 'owner', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getUsers', + }, }, { displayName: 'Phone', name: 'phone', type: 'string', default: '', - description: 'The phone number of the user', }, { - displayName: 'Unsubscribed From Emails', - name: 'unsubscribedFromEmails', - type: 'boolean', - default: false, - description: 'Whether the Lead is unsubscribed from emails', - }, - { - displayName: 'Update Last Request At', - name: 'updateLastRequestAt', - type: 'boolean', - default: false, - description: 'A boolean value, which if true, instructs Intercom to update the
users last_request_at value to the current API service time in
UTC. default value if not sent is false.', - }, - { - displayName: 'Companies', - name: 'companies', - type: 'multiOptions', - typeOptions: { - loadOptionsMethod: 'getCompanies', - }, - default: [], - description: 'Identifies the companies this user belongs to.', - }, - { - displayName: 'UTM Source', - name: 'utmSource', + displayName: 'Salutation', + name: 'salutation', type: 'string', default: '', - description: 'An avatar image URL. note: the image url needs to be https.', }, { - displayName: 'UTM Medium', - name: 'utmMedium', + displayName: 'Secondary Email', + name: 'secondaryEmail', type: 'string', default: '', - description: 'Identifies what type of link was used', }, { - displayName: 'UTM Campaign', - name: 'utmCampaign', + displayName: 'Skype ID', + name: 'SkypeId', type: 'string', default: '', - description: 'Identifies a specific product promotion or strategic campaign', }, { - displayName: 'UTM Term', - name: 'utmTerm', + displayName: 'Title', + name: 'title', type: 'string', default: '', - description: 'Identifies search terms', }, { - displayName: 'UTM Content', - name: 'utmContent', + displayName: 'Twitter', + name: 'twitter', + type: 'string', + default: '', + }, + { + displayName: 'Website', + name: 'website', type: 'string', default: '', - description: 'Identifies what specifically was clicked to bring the user to the site', }, ] }, - + { + displayName: 'Address', + name: 'addressUi', + type: 'fixedCollection', + default: {}, + placeholder: 'Add Address', + typeOptions: { + multipleValues: false, + }, + required: false, + displayOptions: { + show: { + resource: [ + 'lead', + ], + operation: [ + 'create', + ], + }, + }, + options: [ + { + name: 'addressValues', + displayName: 'Address', + values: [ + { + displayName: 'Street', + name: 'street', + type: 'string', + default: '', + }, + { + displayName: 'City', + name: 'city', + type: 'string', + default: '', + }, + { + displayName: 'State', + name: 'state', + type: 'string', + default: '', + }, + { + displayName: 'Country', + name: 'country', + type: 'string', + default: '', + }, + { + displayName: 'Zip Code', + name: 'zipCode', + type: 'string', + default: '', + }, + ], + } + ], + }, +/* -------------------------------------------------------------------------- */ +/* lead:update */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Lead ID', + name: 'leadId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + resource: [ + 'lead', + ], + operation: [ + 'update', + ], + }, + }, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + operation: [ + 'update', + ], + resource: [ + 'lead', + ], + }, + }, + options: [ + { + displayName: 'Annual Revenue', + name: 'annualRevenue', + type: 'number', + typeOptions: { + numberPrecision: 2, + }, + default: 0, + }, + { + displayName: 'Company', + name: 'company', + type: 'string', + default: '', + }, + { + displayName: 'Description', + name: 'description', + type: 'string', + default: '', + }, + { + displayName: 'Email', + name: 'email', + type: 'string', + default: '', + }, + { + displayName: 'Email Opt Out', + name: 'emailOptOut', + type: 'boolean', + default: false, + }, + { + displayName: 'Fax', + name: 'fax', + type: 'string', + default: '', + }, + { + displayName: 'First Name', + name: 'firstName', + type: 'string', + default: '', + }, + { + displayName: 'Industry', + name: 'industry', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getIndustries', + }, + default: '', + }, + { + displayName: 'Is Record Duplicate', + name: 'isRecordDuplicate', + type: 'boolean', + default: false, + }, + { + displayName: 'Last Name', + name: 'lastName', + type: 'string', + default: '', + description: `User's last name`, + }, + { + displayName: 'Lead Source', + name: 'leadSource', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getLeadSources', + }, + default: '', + }, + { + displayName: 'Lead Status', + name: 'leadStatus', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getLeadStatuses' + }, + default: '', + }, + { + displayName: 'Mobile', + name: 'mobile', + type: 'string', + default: '', + }, + { + displayName: 'No. of Employees', + name: 'numberOfEmployees', + type: 'number', + default: 1, + }, + { + displayName: 'Owner', + name: 'owner', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getUsers', + }, + }, + { + displayName: 'Phone', + name: 'phone', + type: 'string', + default: '', + }, + { + displayName: 'Salutation', + name: 'salutation', + type: 'string', + default: '', + }, + { + displayName: 'Secondary Email', + name: 'secondaryEmail', + type: 'string', + default: '', + }, + { + displayName: 'Skype ID', + name: 'SkypeId', + type: 'string', + default: '', + }, + { + displayName: 'Title', + name: 'title', + type: 'string', + default: '', + }, + { + displayName: 'Twitter', + name: 'twitter', + type: 'string', + default: '', + }, + { + displayName: 'Website', + name: 'website', + type: 'string', + default: '', + }, + ] + }, + { + displayName: 'Address', + name: 'addressUi', + type: 'fixedCollection', + default: {}, + placeholder: 'Add Address', + typeOptions: { + multipleValues: false, + }, + required: false, + displayOptions: { + show: { + resource: [ + 'lead', + ], + operation: [ + 'update', + ], + }, + }, + options: [ + { + name: 'addressValues', + displayName: 'Address', + values: [ + { + displayName: 'Street', + name: 'street', + type: 'string', + default: '', + }, + { + displayName: 'City', + name: 'city', + type: 'string', + default: '', + }, + { + displayName: 'State', + name: 'state', + type: 'string', + default: '', + }, + { + displayName: 'Country', + name: 'country', + type: 'string', + default: '', + }, + { + displayName: 'Zip Code', + name: 'zipCode', + type: 'string', + default: '', + }, + ], + } + ], + }, +/* -------------------------------------------------------------------------- */ +/* lead:get */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Lead ID', + name: 'leadId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + resource: [ + 'lead', + ], + operation: [ + 'get', + ], + }, + }, + }, +/* -------------------------------------------------------------------------- */ +/* lead:getAll */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + resource: [ + 'lead', + ], + operation: [ + 'getAll', + ], + }, + }, + default: false, + description: 'If all results should be returned or only up to a given limit.', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + displayOptions: { + show: { + resource: [ + 'lead', + ], + operation: [ + 'getAll', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 200, + }, + default: 100, + description: 'How many results to return.', + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + displayOptions: { + show: { + resource: [ + 'lead', + ], + operation: [ + 'getAll', + ], + }, + }, + options: [ + { + displayName: 'Approved', + name: 'approved', + type: 'boolean', + default: true, + description: 'To get the list of approved records. Default value is true.', + }, + { + displayName: 'Converted', + name: 'converted', + type: 'boolean', + default: false, + description: 'To get the list of converted records. Default value is false', + }, + { + displayName: 'Fields', + name: 'fields', + type: 'multiOptions', + typeOptions: { + loadOptionsMethod: 'getLeadFields', + }, + default: [], + }, + { + displayName: 'Include Child', + name: 'includeChild', + type: 'boolean', + default: false, + description: 'To include records from the child territories. True includes child territory records', + }, + { + displayName: 'Sort By', + name: 'sortBy', + type: 'multiOptions', + typeOptions: { + loadOptionsMethod: 'getLeadFields', + }, + default: [], + }, + { + displayName: 'Sort Order', + name: 'sortOrder', + type: 'options', + options: [ + { + name: 'ASC', + value: 'asc', + }, + { + name: 'DESC', + value: 'desc', + }, + ], + default: 'desc', + description: 'Order sort attribute ascending or descending.', + }, + { + displayName: 'Territory ID', + name: 'territoryId', + type: 'string', + default: '', + description: 'To get the list of records based on the territory ', + }, + ] + }, +/* -------------------------------------------------------------------------- */ +/* lead:delete */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Lead ID', + name: 'leadId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + resource: [ + 'lead', + ], + operation: [ + 'delete', + ], + }, + }, + }, ] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Zoho/LeadInterface.ts b/packages/nodes-base/nodes/Zoho/LeadInterface.ts new file mode 100644 index 0000000000..957f816cb6 --- /dev/null +++ b/packages/nodes-base/nodes/Zoho/LeadInterface.ts @@ -0,0 +1,37 @@ +export interface ILead { + Annual_Revenue?: number; + City?: string; + Company?: string; + Country?: string; + Description?: string; + Designation?: string; + Email?: string; + Email_Opt_Out?: boolean; + Fax?: string; + First_Name?: string; + Industry?: string; + Is_Record_Duplicate?: boolean; + Last_Name?: string; + Lead_Owner?: string; + Lead_Source?: string; + Lead_Status?: string; + Mobile?: string; + No_of_Employees?: number; + Phone?: string; + Salutation?: string; + Secondary_Email?: string; + Skype_ID?: string; + State?: string; + Street?: string; + Twitter?: string; + Website?: string; + Zip_Code?: string; +} + +export interface IAddress { + street?: string; + city?: string; + state?: string; + country?: string; + zipCode?: string; +} diff --git a/packages/nodes-base/nodes/Zoho/ZohoCrm.node.ts b/packages/nodes-base/nodes/Zoho/ZohoCrm.node.ts index f1d397c2dc..2e2a36bee6 100644 --- a/packages/nodes-base/nodes/Zoho/ZohoCrm.node.ts +++ b/packages/nodes-base/nodes/Zoho/ZohoCrm.node.ts @@ -4,9 +4,11 @@ import { import { IDataObject, + ILoadOptionsFunctions, INodeExecutionData, - INodeTypeDescription, + INodePropertyOptions, INodeType, + INodeTypeDescription, } from 'n8n-workflow'; import { @@ -15,10 +17,15 @@ import { } from './GenericFunctions'; import { - leadOperations, leadFields, + leadOperations, } from './LeadDescription'; +import { + IAddress, + ILead, +} from './LeadInterface'; + export class ZohoCrm implements INodeType { description: INodeTypeDescription = { displayName: 'Zoho CRM', @@ -59,34 +66,381 @@ export class ZohoCrm implements INodeType { ], }; + methods = { + loadOptions: { + // Get all the available users to display them to user so that he can + // select them easily + async getUsers(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + const { users } = await zohoApiRequest.call(this, 'GET', '/users', {}, { type: 'AllUsers' }); + for (const user of users) { + const userName = `${user.first_name} ${user.last_name}`; + const userId = user.profile.id; + returnData.push({ + name: userName, + value: userId, + }); + } + return returnData; + }, + // Get all the available accounts to display them to user so that he can + // select them easily + async getAccounts(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + const qs: IDataObject = {}; + qs.sort_by = 'Created_Time'; + qs.sort_order = 'desc'; + const { data } = await zohoApiRequest.call(this, 'GET', '/accounts', {}, qs); + for (const account of data) { + const accountName = account.Account_Name; + const accountId = account.id; + returnData.push({ + name: accountName, + value: accountId, + }); + } + return returnData; + }, + // Get all the available lead statuses to display them to user so that he can + // select them easily + async getLeadStatuses(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + const qs: IDataObject = {}; + qs.module = 'leads'; + const { fields } = await zohoApiRequest.call(this, 'GET', '/settings/fields', {}, qs); + for (const field of fields) { + if (field.api_name === 'Lead_Status') { + for (const value of field.pick_list_values) { + const valueName = value.display_value; + const valueId = value.actual_value; + returnData.push({ + name: valueName, + value: valueId, + }); + return returnData; + } + } + } + return returnData; + }, + // Get all the available lead sources to display them to user so that he can + // select them easily + async getLeadSources(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + const qs: IDataObject = {}; + qs.module = 'leads'; + const { fields } = await zohoApiRequest.call(this, 'GET', '/settings/fields', {}, qs); + for (const field of fields) { + if (field.api_name === 'Lead_Source') { + for (const value of field.pick_list_values) { + const valueName = value.display_value; + const valueId = value.actual_value; + returnData.push({ + name: valueName, + value: valueId, + }); + return returnData; + } + } + } + return returnData; + }, + // Get all the available industries to display them to user so that he can + // select them easily + async getIndustries(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + const qs: IDataObject = {}; + qs.module = 'leads'; + const { fields } = await zohoApiRequest.call(this, 'GET', '/settings/fields', {}, qs); + for (const field of fields) { + if (field.api_name === 'Industry') { + for (const value of field.pick_list_values) { + const valueName = value.display_value; + const valueId = value.actual_value; + returnData.push({ + name: valueName, + value: valueId, + }); + return returnData; + } + } + } + return returnData; + }, + // Get all the available lead fields to display them to user so that he can + // select them easily + async getLeadFields(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + const qs: IDataObject = {}; + qs.module = 'leads'; + const { fields } = await zohoApiRequest.call(this, 'GET', '/settings/fields', {}, qs); + for (const field of fields) { + returnData.push({ + name: field.field_label, + value: field.api_name, + }); + } + return returnData; + }, + }, + }; + async execute(this: IExecuteFunctions): Promise { const items = this.getInputData(); const returnData: IDataObject[] = []; const length = items.length as unknown as number; + const qs: IDataObject = {}; let responseData; for (let i = 0; i < length; i++) { const resource = this.getNodeParameter('resource', 0) as string; const operation = this.getNodeParameter('operation', 0) as string; if (resource === 'lead') { + //https://www.zoho.com/crm/developer/docs/api/insert-records.html if (operation === 'create') { const lastName = this.getNodeParameter('lastName', i) as string; const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; - const body = { + const body: ILead = { Last_Name: lastName, }; - // if (additionalFields.email) { - // // @ts-ignore - // body.email = additionalFields.email as string; - // } + if (additionalFields.owner) { + body.Lead_Owner = additionalFields.owner as string; + } + if (additionalFields.company) { + body.Company = additionalFields.company as string; + } + if (additionalFields.firstName) { + body.First_Name = additionalFields.firstName as string; + } + if (additionalFields.email) { + body.Email = additionalFields.email as string; + } + if (additionalFields.title) { + body.Designation = additionalFields.title as string; + } + if (additionalFields.phone) { + body.Phone = additionalFields.phone as string; + } + if (additionalFields.mobile) { + body.Mobile = additionalFields.mobile as string; + } + if (additionalFields.leadStatus) { + body.Lead_Status = additionalFields.leadStatus as string; + } + if (additionalFields.fax) { + body.Fax = additionalFields.fax as string; + } + if (additionalFields.website) { + body.Website = additionalFields.website as string; + } + if (additionalFields.leadSource) { + body.Lead_Source = additionalFields.leadSource as string; + } + if (additionalFields.industry) { + body.Industry = additionalFields.industry as string; + } + if (additionalFields.numberOfEmployees) { + body.No_of_Employees = additionalFields.numberOfEmployees as number; + } + if (additionalFields.annualRevenue) { + body.Annual_Revenue = additionalFields.annualRevenue as number; + } + if (additionalFields.emailOptOut) { + body.Email_Opt_Out = additionalFields.emailOptOut as boolean; + } + if (additionalFields.skypeId) { + body.Skype_ID = additionalFields.skypeId as string; + } + if (additionalFields.salutation) { + body.Salutation = additionalFields.salutation as string; + } + if (additionalFields.secondaryEmail) { + body.Secondary_Email = additionalFields.secondaryEmail as string; + } + if (additionalFields.twitter) { + body.Twitter = additionalFields.twitter as string; + } + if (additionalFields.isRecordDuplicate) { + body.Is_Record_Duplicate = additionalFields.isRecordDuplicate as boolean; + } + if (additionalFields.description) { + body.Description = additionalFields.description as string; + } + const address = (this.getNodeParameter('addressUi', i) as IDataObject).addressValues as IAddress; + if (address) { + if (address.country) { + body.Country = address.country as string; + } + if (address.city) { + body.City = address.city as string; + } + if (address.state) { + body.State = address.state as string; + } + if (address.street) { + body.Street = address.street as string; + } + if (address.zipCode) { + body.Zip_Code = address.zipCode as string; + } + } responseData = await zohoApiRequest.call(this, 'POST', '/leads', body); responseData = responseData.data; - } else { - throw new Error(`The operation "${operation}" is not known!`); - } - } else { - throw new Error(`The resource "${resource}" is not known!`); - } + if (responseData.length) { + responseData = responseData[0].details; + } + } + //https://www.zoho.com/crm/developer/docs/api/update-specific-record.html + if (operation === 'update') { + const leadId = this.getNodeParameter('leadId', i) as string; + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + const body: ILead = {}; + if (additionalFields.lastName) { + body.Last_Name = additionalFields.lastName as string; + } + if (additionalFields.owner) { + body.Lead_Owner = additionalFields.owner as string; + } + if (additionalFields.company) { + body.Company = additionalFields.company as string; + } + if (additionalFields.firstName) { + body.First_Name = additionalFields.firstName as string; + } + if (additionalFields.email) { + body.Email = additionalFields.email as string; + } + if (additionalFields.title) { + body.Designation = additionalFields.title as string; + } + if (additionalFields.phone) { + body.Phone = additionalFields.phone as string; + } + if (additionalFields.mobile) { + body.Mobile = additionalFields.mobile as string; + } + if (additionalFields.leadStatus) { + body.Lead_Status = additionalFields.leadStatus as string; + } + if (additionalFields.fax) { + body.Fax = additionalFields.fax as string; + } + if (additionalFields.website) { + body.Website = additionalFields.website as string; + } + if (additionalFields.leadSource) { + body.Lead_Source = additionalFields.leadSource as string; + } + if (additionalFields.industry) { + body.Industry = additionalFields.industry as string; + } + if (additionalFields.numberOfEmployees) { + body.No_of_Employees = additionalFields.numberOfEmployees as number; + } + if (additionalFields.annualRevenue) { + body.Annual_Revenue = additionalFields.annualRevenue as number; + } + if (additionalFields.emailOptOut) { + body.Email_Opt_Out = additionalFields.emailOptOut as boolean; + } + if (additionalFields.skypeId) { + body.Skype_ID = additionalFields.skypeId as string; + } + if (additionalFields.salutation) { + body.Salutation = additionalFields.salutation as string; + } + if (additionalFields.secondaryEmail) { + body.Secondary_Email = additionalFields.secondaryEmail as string; + } + if (additionalFields.twitter) { + body.Twitter = additionalFields.twitter as string; + } + if (additionalFields.isRecordDuplicate) { + body.Is_Record_Duplicate = additionalFields.isRecordDuplicate as boolean; + } + if (additionalFields.description) { + body.Description = additionalFields.description as string; + } + const address = (this.getNodeParameter('addressUi', i) as IDataObject).addressValues as IAddress; + if (address) { + if (address.country) { + body.Country = address.country as string; + } + if (address.city) { + body.City = address.city as string; + } + if (address.state) { + body.State = address.state as string; + } + if (address.street) { + body.Street = address.street as string; + } + if (address.zipCode) { + body.Zip_Code = address.zipCode as string; + } + } + responseData = await zohoApiRequest.call(this, 'PUT', `/leads/${leadId}`, body); + responseData = responseData.data; + + if (responseData.length) { + responseData = responseData[0].details; + } + } + //https://www.zoho.com/crm/developer/docs/api/update-specific-record.html + if (operation === 'get') { + const leadId = this.getNodeParameter('leadId', i) as string; + responseData = await zohoApiRequest.call(this, 'GET', `/leads/${leadId}`); + if (responseData !== undefined) { + responseData = responseData.data; + } + + } + //https://www.zoho.com/crm/developer/docs/api/get-records.html + if (operation === 'getAll') { + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + const options = this.getNodeParameter('options', i) as IDataObject; + if (options.fields) { + qs.fields = (options.fields as string[]).join(','); + } + if (options.approved) { + qs.approved = options.approved as boolean; + } + if (options.converted) { + qs.converted = options.converted as boolean; + } + if (options.includeChild) { + qs.include_child = options.includeChild as boolean; + } + if (options.sortOrder) { + qs.sort_order = options.sortOrder as string; + } + if (options.sortBy) { + qs.sort_by = options.sortBy as string; + } + if (options.territoryId) { + qs.territory_id = options.territoryId as string; + } + if (returnAll) { + responseData = await zohoApiRequestAllItems.call(this, 'data', 'GET', '/leads', {}, qs); + } else { + qs.per_page = this.getNodeParameter('limit', i) as number; + responseData = await zohoApiRequest.call(this, 'GET', '/leads', {}, qs); + responseData = responseData.data; + } + } + //https://www.zoho.com/crm/developer/docs/api/delete-specific-record.html + if (operation === 'delete') { + const leadId = this.getNodeParameter('leadId', i) as string; + responseData = await zohoApiRequest.call(this, 'DELETE', `/leads/${leadId}`); + responseData = responseData.data; + } + //https://www.zoho.com/crm/developer/docs/api/field-meta.html + if (operation === 'getFields') { + qs.module = 'leads'; + responseData = await zohoApiRequest.call(this, 'GET', '/settings/fields', {}, qs); + responseData = responseData.fields; + } + } if (Array.isArray(responseData)) { returnData.push.apply(returnData, responseData as IDataObject[]); } else if (responseData !== undefined) { diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 1242479bfd..6791982fad 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -44,6 +44,7 @@ "dist/credentials/GitlabApi.credentials.js", "dist/credentials/GoogleApi.credentials.js", "dist/credentials/GoogleOAuth2Api.credentials.js", + "dist/credentials/HelpScoutOAuth2Api.credentials.js", "dist/credentials/HttpBasicAuth.credentials.js", "dist/credentials/HttpDigestAuth.credentials.js", "dist/credentials/HttpHeaderAuth.credentials.js", @@ -57,6 +58,9 @@ "dist/credentials/MailgunApi.credentials.js", "dist/credentials/MandrillApi.credentials.js", "dist/credentials/MattermostApi.credentials.js", + "dist/credentials/MicrosoftExcelOAuth2Api.credentials.js", + "dist/credentials/MicrosoftOAuth2Api.credentials.js", + "dist/credentials/MicrosoftOneDriveOAuth2Api.credentials.js", "dist/credentials/MongoDb.credentials.js", "dist/credentials/MySql.credentials.js", "dist/credentials/NextCloudApi.credentials.js", @@ -123,6 +127,8 @@ "dist/nodes/Google/GoogleDrive.node.js", "dist/nodes/Google/GoogleSheets.node.js", "dist/nodes/GraphQL/GraphQL.node.js", + "dist/nodes/HelpScout/HelpScout.node.js", + "dist/nodes/HelpScout/HelpScoutTrigger.node.js", "dist/nodes/HtmlExtract/HtmlExtract.node.js", "dist/nodes/HttpRequest.node.js", "dist/nodes/Hubspot/Hubspot.node.js", @@ -139,6 +145,8 @@ "dist/nodes/Mandrill/Mandrill.node.js", "dist/nodes/Mattermost/Mattermost.node.js", "dist/nodes/Merge.node.js", + "dist/nodes/Microsoft/Excel/MicrosoftExcel.node.js", + "dist/nodes/Microsoft/OneDrive/MicrosoftOneDrive.node.js", "dist/nodes/MoveBinaryData.node.js", "dist/nodes/MongoDb/MongoDb.node.js", "dist/nodes/MySql/MySql.node.js",