diff --git a/packages/nodes-base/credentials/ZoomApi.credentials.ts b/packages/nodes-base/credentials/ZoomApi.credentials.ts new file mode 100644 index 000000000..dbef99642 --- /dev/null +++ b/packages/nodes-base/credentials/ZoomApi.credentials.ts @@ -0,0 +1,14 @@ +import { ICredentialType, NodePropertyTypes } from 'n8n-workflow'; + +export class ZoomApi implements ICredentialType { + name = 'zoomApi'; + displayName = 'Zoom API'; + properties = [ + { + displayName: 'JTW Token', + name: 'accessToken', + type: 'string' as NodePropertyTypes, + default: '' + } + ]; +} diff --git a/packages/nodes-base/credentials/ZoomOAuth2Api.credentials.ts b/packages/nodes-base/credentials/ZoomOAuth2Api.credentials.ts new file mode 100644 index 000000000..f85cc7525 --- /dev/null +++ b/packages/nodes-base/credentials/ZoomOAuth2Api.credentials.ts @@ -0,0 +1,42 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + +export class ZoomOAuth2Api implements ICredentialType { + name = 'zoomOAuth2Api'; + extends = ['oAuth2Api']; + displayName = 'Zoom OAuth2 API'; + properties = [ + { + displayName: 'Authorization URL', + name: 'authUrl', + type: 'hidden' as NodePropertyTypes, + default: 'https://zoom.us/oauth/authorize' + }, + { + displayName: 'Access Token URL', + name: 'accessTokenUrl', + type: 'hidden' as NodePropertyTypes, + default: 'https://zoom.us/oauth/token' + }, + { + 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: 'header' + } + ]; +} diff --git a/packages/nodes-base/gulpfile.js b/packages/nodes-base/gulpfile.js index 9771a4017..58ba6ec51 100644 --- a/packages/nodes-base/gulpfile.js +++ b/packages/nodes-base/gulpfile.js @@ -1,7 +1,7 @@ const { src, dest } = require('gulp'); function copyIcons() { - return src('nodes/**/*.png') + return src('nodes/**/*.{png,svg}') .pipe(dest('dist/nodes')); } diff --git a/packages/nodes-base/nodes/Salesforce/GenericFunctions.ts b/packages/nodes-base/nodes/Salesforce/GenericFunctions.ts index 648735feb..4c8143223 100644 --- a/packages/nodes-base/nodes/Salesforce/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Salesforce/GenericFunctions.ts @@ -30,7 +30,7 @@ export async function salesforceApiRequest(this: IExecuteFunctions | IExecuteSin } } -export async function salesforceApiRequestAllItems(this: IExecuteFunctions | ILoadOptionsFunctions, propertyName: string ,method: string, endpoint: string, body: any = {}, query: IDataObject = {}): Promise { // tslint:disable-line:no-any +export async function salesforceApiRequestAllItems(this: IExecuteFunctions | ILoadOptionsFunctions, propertyName: string, method: string, endpoint: string, body: any = {}, query: IDataObject = {}): Promise { // tslint:disable-line:no-any const returnData: IDataObject[] = []; diff --git a/packages/nodes-base/nodes/Slack/Slack.node.ts b/packages/nodes-base/nodes/Slack/Slack.node.ts index d6df14ab4..904bf960f 100644 --- a/packages/nodes-base/nodes/Slack/Slack.node.ts +++ b/packages/nodes-base/nodes/Slack/Slack.node.ts @@ -289,7 +289,7 @@ export class Slack implements INodeType { if (operation === 'get') { const channel = this.getNodeParameter('channelId', i) as string; qs.channel = channel, - responseData = await slackApiRequest.call(this, 'POST', '/conversations.info', {}, qs); + responseData = await slackApiRequest.call(this, 'POST', '/conversations.info', {}, qs); responseData = responseData.channel; } //https://api.slack.com/methods/conversations.list @@ -457,7 +457,7 @@ export class Slack implements INodeType { if (!jsonParameters) { const attachments = this.getNodeParameter('attachments', i, []) as unknown as Attachment[]; - const blocksUi = (this.getNodeParameter('blocksUi', i, []) as IDataObject).blocksValues as IDataObject[]; + const blocksUi = (this.getNodeParameter('blocksUi', i, []) as IDataObject).blocksValues as IDataObject[]; // The node does save the fields data differently than the API // expects so fix the data befre we send the request @@ -483,7 +483,7 @@ export class Slack implements INodeType { block.block_id = blockUi.blockId as string; block.type = blockUi.type as string; if (block.type === 'actions') { - const elementsUi = (blockUi.elementsUi as IDataObject).elementsValues as IDataObject[]; + const elementsUi = (blockUi.elementsUi as IDataObject).elementsValues as IDataObject[]; if (elementsUi) { for (const elementUi of elementsUi) { const element: Element = {}; @@ -499,7 +499,7 @@ export class Slack implements INodeType { text: elementUi.text as string, type: 'plain_text', emoji: elementUi.emoji as boolean, - }; + }; if (elementUi.url) { element.url = elementUi.url as string; } @@ -509,13 +509,13 @@ export class Slack implements INodeType { if (elementUi.style !== 'default') { element.style = elementUi.style as string; } - const confirmUi = (elementUi.confirmUi as IDataObject).confirmValue as IDataObject; - if (confirmUi) { + const confirmUi = (elementUi.confirmUi as IDataObject).confirmValue as IDataObject; + if (confirmUi) { const confirm: Confirm = {}; - const titleUi = (confirmUi.titleUi as IDataObject).titleValue as IDataObject; - const textUi = (confirmUi.textUi as IDataObject).textValue as IDataObject; - const confirmTextUi = (confirmUi.confirmTextUi as IDataObject).confirmValue as IDataObject; - const denyUi = (confirmUi.denyUi as IDataObject).denyValue as IDataObject; + const titleUi = (confirmUi.titleUi as IDataObject).titleValue as IDataObject; + const textUi = (confirmUi.textUi as IDataObject).textValue as IDataObject; + const confirmTextUi = (confirmUi.confirmTextUi as IDataObject).confirmValue as IDataObject; + const denyUi = (confirmUi.denyUi as IDataObject).denyValue as IDataObject; const style = confirmUi.style as string; if (titleUi) { confirm.title = { @@ -549,13 +549,13 @@ export class Slack implements INodeType { confirm.style = style as string; } element.confirm = confirm; - } - elements.push(element); + } + elements.push(element); } block.elements = elements; } } else if (block.type === 'section') { - const textUi = (blockUi.textUi as IDataObject).textValue as IDataObject; + const textUi = (blockUi.textUi as IDataObject).textValue as IDataObject; if (textUi) { const text: Text = {}; if (textUi.type === 'plainText') { @@ -570,7 +570,7 @@ export class Slack implements INodeType { } else { throw new Error('Property text must be defined'); } - const fieldsUi = (blockUi.fieldsUi as IDataObject).fieldsValues as IDataObject[]; + const fieldsUi = (blockUi.fieldsUi as IDataObject).fieldsValues as IDataObject[]; if (fieldsUi) { const fields: Text[] = []; for (const fieldUi of fieldsUi) { @@ -590,7 +590,7 @@ export class Slack implements INodeType { block.fields = fields; } } - const accessoryUi = (blockUi.accessoryUi as IDataObject).accessoriesValues as IDataObject; + const accessoryUi = (blockUi.accessoryUi as IDataObject).accessoriesValues as IDataObject; if (accessoryUi) { const accessory: Element = {}; if (accessoryUi.type === 'button') { @@ -609,46 +609,46 @@ export class Slack implements INodeType { if (accessoryUi.style !== 'default') { accessory.style = accessoryUi.style as string; } - const confirmUi = (accessoryUi.confirmUi as IDataObject).confirmValue as IDataObject; + const confirmUi = (accessoryUi.confirmUi as IDataObject).confirmValue as IDataObject; if (confirmUi) { - const confirm: Confirm = {}; - const titleUi = (confirmUi.titleUi as IDataObject).titleValue as IDataObject; - const textUi = (confirmUi.textUi as IDataObject).textValue as IDataObject; - const confirmTextUi = (confirmUi.confirmTextUi as IDataObject).confirmValue as IDataObject; - const denyUi = (confirmUi.denyUi as IDataObject).denyValue as IDataObject; - const style = confirmUi.style as string; - if (titleUi) { - confirm.title = { - type: 'plain_text', - text: titleUi.text as string, - emoji: titleUi.emoji as boolean, - }; - } - if (textUi) { - confirm.text = { - type: 'plain_text', - text: textUi.text as string, - emoji: textUi.emoji as boolean, - }; - } - if (confirmTextUi) { - confirm.confirm = { - type: 'plain_text', - text: confirmTextUi.text as string, - emoji: confirmTextUi.emoji as boolean, - }; - } - if (denyUi) { - confirm.deny = { - type: 'plain_text', - text: denyUi.text as string, - emoji: denyUi.emoji as boolean, - }; - } - if (style !== 'default') { - confirm.style = style as string; - } - accessory.confirm = confirm; + const confirm: Confirm = {}; + const titleUi = (confirmUi.titleUi as IDataObject).titleValue as IDataObject; + const textUi = (confirmUi.textUi as IDataObject).textValue as IDataObject; + const confirmTextUi = (confirmUi.confirmTextUi as IDataObject).confirmValue as IDataObject; + const denyUi = (confirmUi.denyUi as IDataObject).denyValue as IDataObject; + const style = confirmUi.style as string; + if (titleUi) { + confirm.title = { + type: 'plain_text', + text: titleUi.text as string, + emoji: titleUi.emoji as boolean, + }; + } + if (textUi) { + confirm.text = { + type: 'plain_text', + text: textUi.text as string, + emoji: textUi.emoji as boolean, + }; + } + if (confirmTextUi) { + confirm.confirm = { + type: 'plain_text', + text: confirmTextUi.text as string, + emoji: confirmTextUi.emoji as boolean, + }; + } + if (denyUi) { + confirm.deny = { + type: 'plain_text', + text: denyUi.text as string, + emoji: denyUi.emoji as boolean, + }; + } + if (style !== 'default') { + confirm.style = style as string; + } + accessory.confirm = confirm; } } block.accessory = accessory; @@ -787,8 +787,8 @@ export class Slack implements INodeType { if (binaryData) { const binaryPropertyName = this.getNodeParameter('binaryPropertyName', i) as string; if (items[i].binary === undefined - //@ts-ignore - || items[i].binary[binaryPropertyName] === undefined) { + //@ts-ignore + || items[i].binary[binaryPropertyName] === undefined) { throw new Error(`No binary data property "${binaryPropertyName}" does not exists on item!`); } body.file = { @@ -801,7 +801,7 @@ export class Slack implements INodeType { contentType: items[i].binary[binaryPropertyName].mimeType, } }; - responseData = await slackApiRequest.call(this, 'POST', '/files.upload', {}, qs, { 'Content-Type': 'multipart/form-data' }, { formData: body }); + responseData = await slackApiRequest.call(this, 'POST', '/files.upload', {}, qs, { 'Content-Type': 'multipart/form-data' }, { formData: body }); responseData = responseData.file; } else { const fileContent = this.getNodeParameter('fileContent', i) as string; diff --git a/packages/nodes-base/nodes/Zoom/GenericFunctions.ts b/packages/nodes-base/nodes/Zoom/GenericFunctions.ts new file mode 100644 index 000000000..95e07f09b --- /dev/null +++ b/packages/nodes-base/nodes/Zoom/GenericFunctions.ts @@ -0,0 +1,105 @@ +import { + OptionsWithUri, +} from 'request'; + +import { + IExecuteFunctions, + IExecuteSingleFunctions, + ILoadOptionsFunctions, +} from 'n8n-core'; + +import { + IDataObject, +} from 'n8n-workflow'; + +export async function zoomApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: object = {}, query: object = {}, headers: {} | undefined = undefined, option: {} = {}): Promise { // tslint:disable-line:no-any + + const authenticationMethod = this.getNodeParameter('authentication', 0, 'accessToken') as string; + + let options: OptionsWithUri = { + method, + headers: headers || { + 'Content-Type': 'application/json' + }, + body, + qs: query, + uri: `https://api.zoom.us/v2${resource}`, + json: true + }; + options = Object.assign({}, options, option); + if (Object.keys(body).length === 0) { + delete options.body; + } + if (Object.keys(query).length === 0) { + delete options.qs; + } + + try { + if (authenticationMethod === 'accessToken') { + const credentials = this.getCredentials('zoomApi'); + if (credentials === undefined) { + throw new Error('No credentials got returned!'); + } + options.headers!.Authorization = `Bearer ${credentials.accessToken}`; + + //@ts-ignore + return await this.helpers.request(options); + } else { + //@ts-ignore + return await this.helpers.requestOAuth2.call(this, 'zoomOAuth2Api', options); + } + } catch (error) { + if (error.statusCode === 401) { + // Return a clear error + throw new Error('The Zoom credentials are not valid!'); + } + + if (error.response && error.response.body && error.response.body.message) { + // Try to return the error prettier + throw new Error(`Zoom error response [${error.statusCode}]: ${error.response.body.message}`); + } + + // If that data does not exist for some reason return the actual error + throw error; + } +} + + +export async function zoomApiRequestAllItems( + 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.page_number = 0; + do { + responseData = await zoomApiRequest.call( + this, + method, + endpoint, + body, + query + ); + query.page_number++; + returnData.push.apply(returnData, responseData[propertyName]); + // zoom free plan rate limit is 1 request/second + // TODO just wait when the plan is free + await wait(); + } while ( + responseData.page_count !== responseData.page_number + ); + + return returnData; +} +function wait() { + return new Promise((resolve, reject) => { + setTimeout(() => { + resolve(true); + }, 1000); + }); +} diff --git a/packages/nodes-base/nodes/Zoom/MeetingDescription.ts b/packages/nodes-base/nodes/Zoom/MeetingDescription.ts new file mode 100644 index 000000000..f41223539 --- /dev/null +++ b/packages/nodes-base/nodes/Zoom/MeetingDescription.ts @@ -0,0 +1,751 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +export const meetingOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'meeting', + ], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create a meeting', + }, + { + name: 'Delete', + value: 'delete', + description: 'Delete a meeting', + }, + { + name: 'Get', + value: 'get', + description: 'Retrieve a meeting', + }, + { + name: 'Get All', + value: 'getAll', + description: 'Retrieve all meetings', + }, + { + name: 'Update', + value: 'update', + description: 'Update a meeting', + }, + ], + default: 'create', + description: 'The operation to perform.', + }, +] as INodeProperties[]; + +export const meetingFields = [ + /* -------------------------------------------------------------------------- */ + /* meeting:create */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Topic', + name: 'topic', + type: 'string', + typeOptions: { + alwaysOpenEditWindow: true, + }, + default: '', + displayOptions: { + show: { + operation: [ + 'create', + + ], + resource: [ + 'meeting', + ], + }, + }, + description: `Topic of the meeting.`, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + operation: [ + 'create', + + ], + resource: [ + 'meeting', + ], + }, + }, + options: [ + { + displayName: 'Agenda', + name: 'agenda', + type: 'string', + typeOptions: { + alwaysOpenEditWindow: true, + }, + default: '', + description: 'Meeting agenda.', + }, + { + displayName: 'Duration', + name: 'duration', + type: 'number', + typeOptions: { + minValue: 0, + }, + default: 0, + description: 'Meeting duration (minutes).', + }, + { + displayName: 'Password', + name: 'password', + type: 'string', + default: '', + description: 'Password to join the meeting with maximum 10 characters.', + }, + { + displayName: 'Schedule For', + name: 'scheduleFor', + type: 'string', + default: '', + description: 'Schedule meeting for someone else from your account, provide their email ID.', + }, + { + displayName: 'Settings', + name: 'settings', + type: 'collection', + placeholder: 'Add Setting', + default: {}, + options: [ + { + displayName: 'Audio', + name: 'audio', + type: 'options', + options: [ + { + name: 'Both Telephony and VoiP', + value: 'both', + }, + { + name: 'Telephony', + value: 'telephony', + }, + { + name: 'VOIP', + value: 'voip', + }, + + ], + default: 'both', + description: 'Determine how participants can join audio portion of the meeting.', + }, + { + displayName: 'Alternative Hosts', + name: 'alternativeHosts', + type: 'string', + default: '', + description: 'Alternative hosts email IDs.', + }, + { + displayName: 'Auto Recording', + name: 'autoRecording', + type: 'options', + options: [ + { + name: 'Record on Local', + value: 'local', + }, + { + name: 'Record on Cloud', + value: 'cloud', + }, + { + name: 'Disabled', + value: 'none', + }, + ], + default: 'none', + description: 'Auto recording.', + }, + { + displayName: 'Host Meeting in China', + name: 'cnMeeting', + type: 'boolean', + default: false, + description: 'Host Meeting in China.', + }, + { + displayName: 'Host Meeting in India', + name: 'inMeeting', + type: 'boolean', + default: false, + description: 'Host Meeting in India.', + }, + { + displayName: 'Host Video', + name: 'hostVideo', + type: 'boolean', + default: false, + description: 'Start video when host joins the meeting.', + }, + { + displayName: 'Join Before Host', + name: 'joinBeforeHost', + type: 'boolean', + default: false, + description: 'Allow participants to join the meeting before host starts it.', + }, + { + displayName: 'Muting Upon Entry', + name: 'muteUponEntry', + type: 'boolean', + default: false, + description: 'Mute participants upon entry.', + }, + { + displayName: 'Participant Video', + name: 'participantVideo', + type: 'boolean', + default: false, + description: 'Start video when participant joins the meeting.', + }, + { + displayName: 'Registration Type', + name: 'registrationType', + type: 'options', + options: [ + { + name: 'Attendees register once and can attend any of the occurences', + value: 1, + }, + { + name: 'Attendees need to register for every occurrence', + value: 2, + }, + { + name: 'Attendees register once and can choose one or more occurrences to attend', + value: 3, + }, + ], + default: 1, + description: 'Registration type. Used for recurring meetings with fixed time only', + }, + { + displayName: 'Watermark', + name: 'watermark', + type: 'boolean', + default: false, + description: 'Adds watermark when viewing a shared screen.', + }, + ], + }, + { + displayName: 'Start Time', + name: 'startTime', + type: 'dateTime', + default: '', + description: 'Start time should be used only for scheduled or recurring meetings with fixed time', + }, + { + displayName: 'Timezone', + name: 'timeZone', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getTimezones', + }, + default: '', + description: `Time zone used in the response. The default is the time zone of the calendar.`, + }, + { + displayName: 'Type', + name: 'type', + type: 'options', + options: [ + { + name: 'Instant Meeting', + value: 1, + }, + { + name: 'Scheduled Meeting', + value: 2, + }, + { + name: 'Recurring Meeting with no fixed time', + value: 3, + }, + { + name: 'Recurring Meeting with fixed time', + value: 8, + }, + + ], + default: 2, + description: 'Meeting type.', + }, + ], + }, + /* -------------------------------------------------------------------------- */ + /* meeting:get */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'ID', + name: 'meetingId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'get', + ], + resource: [ + 'meeting', + ], + }, + }, + description: 'Meeting ID.', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + operation: [ + 'get', + + ], + resource: [ + 'meeting', + ], + }, + }, + options: [ + { + displayName: 'Occurrence ID', + name: 'occurrenceId', + type: 'string', + default: '', + description: 'To view meeting details of a particular occurrence of the recurring meeting.', + }, + { + displayName: 'Show Previous Occurrences', + name: 'showPreviousOccurrences', + type: 'boolean', + default: '', + description: 'To view meeting details of all previous occurrences of the recurring meeting.', + }, + ], + }, + /* -------------------------------------------------------------------------- */ + /* meeting:getAll */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'meeting', + ], + }, + }, + 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: [ + 'meeting', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 300, + }, + default: 30, + description: 'How many results to return.', + }, + { + displayName: 'Filters', + name: 'filters', + type: 'collection', + placeholder: 'Add Filter', + default: {}, + displayOptions: { + show: { + operation: [ + 'getAll', + + ], + resource: [ + 'meeting', + ], + }, + }, + options: [ + { + displayName: 'Type', + name: 'type', + type: 'options', + options: [ + { + name: 'Scheduled', + value: 'scheduled', + description: 'This includes all valid past meetings, live meetings and upcoming scheduled meetings' + }, + { + name: 'Live', + value: 'live', + description: 'All ongoing meetings', + }, + { + name: 'Upcoming', + value: 'upcoming', + description: 'All upcoming meetings including live meetings', + }, + ], + default: 'live', + description: `Meeting type.`, + }, + ], + }, + /* -------------------------------------------------------------------------- */ + /* meeting:delete */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'ID', + name: 'meetingId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'delete', + ], + resource: [ + 'meeting', + ], + }, + }, + description: 'Meeting ID.', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + operation: [ + 'delete', + ], + resource: [ + 'meeting', + ], + }, + }, + options: [ + { + displayName: 'Occurence ID', + name: 'occurrenceId', + type: 'string', + default: '', + description: 'Meeting occurrence ID.', + }, + { + displayName: 'Schedule Reminder', + name: 'scheduleForReminder', + type: 'boolean', + default: false, + description: 'Notify hosts and alternative hosts about meeting cancellation via email', + }, + ], + + }, + /* -------------------------------------------------------------------------- */ + /* meeting:update */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'ID', + name: 'meetingId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'update', + ], + resource: [ + 'meeting', + ], + }, + }, + description: 'Meeting ID.', + }, + { + displayName: 'Update Fields', + name: 'updateFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + operation: [ + 'update', + ], + resource: [ + 'meeting', + ], + }, + }, + options: [ + { + displayName: 'Agenda', + name: 'agenda', + type: 'string', + typeOptions: { + alwaysOpenEditWindow: true, + }, + default: '', + description: 'Meeting agenda.', + }, + { + displayName: 'Duration', + name: 'duration', + type: 'number', + typeOptions: { + minValue: 0, + }, + default: 0, + description: 'Meeting duration (minutes).', + }, + { + displayName: 'Password', + name: 'password', + type: 'string', + default: '', + description: 'Password to join the meeting with maximum 10 characters.', + }, + { + displayName: 'Schedule For', + name: 'scheduleFor', + type: 'string', + default: '', + description: 'Schedule meeting for someone else from your account, provide their email ID.', + }, + { + displayName: 'Settings', + name: 'settings', + type: 'collection', + placeholder: 'Add Setting', + default: {}, + options: [ + { + displayName: 'Audio', + name: 'audio', + type: 'options', + options: [ + { + name: 'Both Telephony and VoiP', + value: 'both', + }, + { + name: 'Telephony', + value: 'telephony', + }, + { + name: 'VOIP', + value: 'voip', + }, + + ], + default: 'both', + description: 'Determine how participants can join audio portion of the meeting.', + }, + { + displayName: 'Alternative Hosts', + name: 'alternativeHosts', + type: 'string', + default: '', + description: 'Alternative hosts email IDs.', + }, + { + displayName: 'Auto Recording', + name: 'autoRecording', + type: 'options', + options: [ + { + name: 'Record on Local', + value: 'local', + }, + { + name: 'Record on Cloud', + value: 'cloud', + }, + { + name: 'Disabled', + value: 'none', + }, + ], + default: 'none', + description: 'Auto recording.', + }, + { + displayName: 'Host Meeting in China', + name: 'cnMeeting', + type: 'boolean', + default: false, + description: 'Host Meeting in China.', + }, + { + displayName: 'Host Meeting in India', + name: 'inMeeting', + type: 'boolean', + default: false, + description: 'Host Meeting in India.', + }, + { + displayName: 'Host Video', + name: 'hostVideo', + type: 'boolean', + default: false, + description: 'Start video when host joins the meeting.', + }, + { + displayName: 'Join Before Host', + name: 'joinBeforeHost', + type: 'boolean', + default: false, + description: 'Allow participants to join the meeting before host starts it.', + }, + { + displayName: 'Muting Upon Entry', + name: 'muteUponEntry', + type: 'boolean', + default: false, + description: 'Mute participants upon entry.', + }, + { + displayName: 'Participant Video', + name: 'participantVideo', + type: 'boolean', + default: false, + description: 'Start video when participant joins the meeting.', + }, + { + displayName: 'Registration Type', + name: 'registrationType', + type: 'options', + options: [ + { + name: 'Attendees register once and can attend any of the occurences', + value: 1, + }, + { + name: 'Attendees need to register for every occurrence', + value: 2, + }, + { + name: 'Attendees register once and can choose one or more occurrences to attend', + value: 3, + }, + ], + default: 1, + description: 'Registration type. Used for recurring meetings with fixed time only', + }, + { + displayName: 'Watermark', + name: 'watermark', + type: 'boolean', + default: false, + description: 'Adds watermark when viewing a shared screen.', + }, + ], + }, + { + displayName: 'Start Time', + name: 'startTime', + type: 'dateTime', + default: '', + description: 'Start time should be used only for scheduled or recurring meetings with fixed time', + }, + { + displayName: 'Timezone', + name: 'timeZone', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getTimezones', + }, + default: '', + description: `Time zone used in the response. The default is the time zone of the calendar.`, + }, + { + displayName: 'Topic', + name: 'topic', + type: 'string', + default: '', + description: `Meeting topic.`, + }, + { + displayName: 'Type', + name: 'type', + type: 'options', + options: [ + { + name: 'Instant Meeting', + value: 1, + }, + { + name: 'Scheduled Meeting', + value: 2, + }, + { + name: 'Recurring Meeting with no fixed time', + value: 3, + }, + { + name: 'Recurring Meeting with fixed time', + value: 8, + }, + + ], + default: 2, + description: 'Meeting type.', + }, + ], + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Zoom/MeetingRegistrantDescription.ts b/packages/nodes-base/nodes/Zoom/MeetingRegistrantDescription.ts new file mode 100644 index 000000000..5438f18fa --- /dev/null +++ b/packages/nodes-base/nodes/Zoom/MeetingRegistrantDescription.ts @@ -0,0 +1,443 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +export const meetingRegistrantOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'meetingRegistrant', + ], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create Meeting Registrants', + }, + { + name: 'Update', + value: 'update', + description: 'Update Meeting Registrant Status', + }, + { + name: 'Get All', + value: 'getAll', + description: 'Retrieve all Meeting Registrants', + }, + + ], + default: 'create', + description: 'The operation to perform.', + } +] as INodeProperties[]; + +export const meetingRegistrantFields = [ + /* -------------------------------------------------------------------------- */ + /* meetingRegistrant:create */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Meeting Id', + name: 'meetingId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'meetingRegistrant', + ], + }, + }, + description: 'Meeting ID.', + }, + { + displayName: 'Email', + name: 'email', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'meetingRegistrant', + ], + }, + }, + description: 'Valid Email-ID.', + }, + { + displayName: 'First Name', + name: 'firstName', + required: true, + type: 'string', + default: '', + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'meetingRegistrant', + ], + }, + }, + description: 'First Name.', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + operation: [ + 'create', + + ], + resource: [ + 'meetingRegistrant', + ], + }, + }, + options: [ + { + displayName: 'Address', + name: 'address', + type: 'string', + default: '', + description: 'Valid address of registrant.', + }, + { + displayName: 'City', + name: 'city', + type: 'string', + default: '', + description: 'Valid city of registrant.', + }, + { + displayName: 'Comments', + name: 'comments', + type: 'string', + default: '', + description: 'Allows registrants to provide any questions they have.', + }, + { + displayName: 'Country', + name: 'country', + type: 'string', + default: '', + description: 'Valid country of registrant.', + }, + { + displayName: 'Job Title', + name: 'jobTitle', + type: 'string', + default: '', + description: 'Job title of registrant.', + }, + { + displayName: 'Last Name', + name: 'lastName', + type: 'string', + default: '', + description: 'Last Name.', + }, + { + displayName: 'Occurrence IDs', + name: 'occurrenceId', + type: 'string', + default: '', + description: 'Occurrence IDs separated by comma.', + }, + { + displayName: 'Organization', + name: 'org', + type: 'string', + default: '', + description: 'Organization of registrant.', + }, + { + displayName: 'Phone Number', + name: 'phone', + type: 'string', + default: '', + description: 'Valid phone number of registrant.', + }, + { + displayName: 'Purchasing Time Frame', + name: 'purchasingTimeFrame', + type: 'options', + options: [ + { + name: 'Within a month', + value: 'Within a month', + }, + { + name: '1-3 months', + value: '1-3 months', + }, + { + name: '4-6 months', + value: '4-6 months', + }, + { + name: 'More than 6 months', + value: 'More than 6 months', + }, + { + name: 'No timeframe', + value: 'No timeframe', + }, + ], + default: '', + description: 'Meeting type.', + }, + { + displayName: 'Role in Purchase Process', + name: 'roleInPurchaseProcess', + type: 'options', + options: [ + { + name: 'Decision Maker', + value: 'Decision Maker', + }, + { + name: 'Evaluator/Recommender', + value: 'Evaluator/Recommender', + }, + { + name: 'Influener', + value: 'Influener', + }, + { + name: 'Not Involved', + value: 'Not Involved', + }, + + ], + default: '', + description: 'Role in purchase process.', + }, + { + displayName: 'State', + name: 'state', + type: 'string', + default: '', + description: 'Valid state of registrant.', + }, + { + displayName: 'Zip Code', + name: 'zip', + type: 'string', + default: '', + description: 'Valid zip-code of registrant.', + }, + + ], + }, + /* -------------------------------------------------------------------------- */ + /* meetingRegistrant:getAll */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Meeting ID', + name: 'meetingId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'meetingRegistrant', + ], + }, + }, + description: 'Meeting ID.', + }, + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'meetingRegistrant', + ], + }, + }, + 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: [ + 'meetingRegistrant', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 300, + }, + default: 30, + description: 'How many results to return.', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + operation: [ + 'getAll', + + ], + resource: [ + 'meetingRegistrant', + ], + }, + }, + options: [ + { + displayName: 'Occurrence ID', + name: 'occurrenceId', + type: 'string', + default: '', + description: `Occurrence ID.`, + }, + { + displayName: 'Status', + name: 'status', + type: 'options', + options: [ + { + name: 'Pending', + value: 'pending', + }, + { + name: 'Approved', + value: 'approved', + }, + { + name: 'Denied', + value: 'denied', + }, + ], + default: 'approved', + description: `Registrant Status.`, + }, + + ] + }, + /* -------------------------------------------------------------------------- */ + /* meetingRegistrant:update */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Meeting ID', + name: 'meetingId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'update', + ], + resource: [ + 'meetingRegistrant', + ], + }, + }, + description: 'Meeting ID.', + }, + { + displayName: 'Action', + name: 'action', + type: 'options', + required: true, + displayOptions: { + show: { + operation: [ + 'update', + ], + resource: [ + 'meetingRegistrant', + ], + }, + }, + options: [ + { + name: 'Cancel', + value: 'cancel', + }, + { + name: 'Approved', + value: 'approve', + }, + { + name: 'Deny', + value: 'deny', + }, + ], + default: '', + description: `Registrant Status.`, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + operation: [ + 'update', + ], + resource: [ + 'meetingRegistrant', + ], + }, + }, + options: [ + { + displayName: 'Occurrence ID', + name: 'occurrenceId', + type: 'string', + default: '', + description: 'Occurrence ID.', + }, + + ], + } + +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Zoom/WebinarDescription.ts b/packages/nodes-base/nodes/Zoom/WebinarDescription.ts new file mode 100644 index 000000000..421eb2cf5 --- /dev/null +++ b/packages/nodes-base/nodes/Zoom/WebinarDescription.ts @@ -0,0 +1,665 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +export const webinarOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'webinar', + ], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create a webinar', + }, + { + name: 'Delete', + value: 'delete', + description: 'Delete a webinar', + }, + { + name: 'Get', + value: 'get', + description: 'Retrieve a webinar', + }, + { + name: 'Get All', + value: 'getAll', + description: 'Retrieve all webinars', + }, + { + name: 'Update', + value: 'update', + description: 'Update a webinar', + } + ], + default: 'create', + description: 'The operation to perform.', + } +] as INodeProperties[]; + +export const webinarFields = [ + /* -------------------------------------------------------------------------- */ + /* webinar:create */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'User ID', + name: 'userId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'webinar', + ], + }, + }, + description: 'User ID or email ID.', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + operation: [ + 'create', + + ], + resource: [ + 'webinar', + ], + } + }, + options: [ + { + displayName: 'Agenda', + name: 'agenda', + type: 'string', + default: '', + description: 'Webinar agenda.', + }, + { + displayName: 'Alternative Hosts', + name: 'alternativeHosts', + type: 'string', + default: '', + description: 'Alternative hosts email IDs.', + }, + { + displayName: 'Approval Type', + name: 'approvalType', + type: 'options', + options: [ + { + name: 'Automatically Approve', + value: 0, + }, + { + name: 'Manually Approve', + value: 1, + }, + { + name: 'No Registration Required', + value: 2, + }, + ], + default: 2, + description: 'Approval type.', + }, + { + displayName: 'Audio', + name: 'audio', + type: 'options', + options: [ + { + name: 'Both Telephony and VoiP', + value: 'both', + }, + { + name: 'Telephony', + value: 'telephony', + }, + { + name: 'VOIP', + value: 'voip', + }, + + ], + default: 'both', + description: 'Determine how participants can join audio portion of the webinar.', + }, + { + displayName: 'Auto Recording', + name: 'autoRecording', + type: 'options', + options: [ + { + name: 'Record on Local', + value: 'local', + }, + { + name: 'Record on Cloud', + value: 'cloud', + }, + { + name: 'Disabled', + value: 'none', + }, + ], + default: 'none', + description: 'Auto recording.', + }, + { + displayName: 'Duration', + name: 'duration', + type: 'string', + default: '', + description: 'Duration.', + }, + { + displayName: 'Host Video', + name: 'hostVideo', + type: 'boolean', + default: false, + description: 'Start video when host joins the webinar.', + }, + { + displayName: 'Panelists Video', + name: 'panelistsVideo', + type: 'boolean', + default: false, + description: 'Start video when panelists joins the webinar.', + }, + { + displayName: 'Password', + name: 'password', + type: 'string', + default: '', + description: 'Password to join the webinar with maximum 10 characters.', + }, + { + displayName: 'Practice Session', + name: 'practiceSession', + type: 'boolean', + default: false, + description: 'Enable Practice session.', + }, + { + displayName: 'Registration Type', + name: 'registrationType', + type: 'options', + options: [ + { + name: 'Attendees register once and can attend any of the occurences', + value: 1, + }, + { + name: 'Attendees need to register for every occurrence', + value: 2, + }, + { + name: 'Attendees register once and can choose one or more occurrences to attend', + value: 3, + }, + ], + default: 1, + description: 'Registration type. Used for recurring webinar with fixed time only', + }, + { + displayName: 'Start Time', + name: 'startTime', + type: 'dateTime', + default: '', + description: 'Start time should be used only for scheduled or recurring webinar with fixed time', + }, + { + displayName: 'Timezone', + name: 'timeZone', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getTimezones', + }, + default: '', + description: `Time zone used in the response. The default is the time zone of the calendar.`, + }, + { + displayName: 'Webinar Topic', + name: 'topic', + type: 'string', + default: '', + description: `Webinar topic.`, + }, + { + displayName: 'Webinar Type', + name: 'type', + type: 'options', + options: [ + { + name: 'Webinar', + value: 5, + }, + { + name: 'Recurring webinar with no fixed time', + value: 6, + }, + { + name: 'Recurring webinar with fixed time', + value: 9, + }, + ], + default: 5, + description: 'Webinar type.', + }, + + ], + }, + /* -------------------------------------------------------------------------- */ + /* webinar:get */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Webinar ID', + name: 'webinarId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'get', + ], + resource: [ + 'webinar', + ], + }, + }, + description: 'Webinar ID.', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + operation: [ + 'get', + + ], + resource: [ + 'webinar', + ], + }, + }, + options: [ + { + displayName: 'Occurence ID', + name: 'occurenceId', + type: 'string', + default: '', + description: 'To view webinar details of a particular occurrence of the recurring webinar.', + }, + { + displayName: 'Show Previous Occurrences', + name: 'showPreviousOccurrences', + type: 'boolean', + default: '', + description: 'To view webinar details of all previous occurrences of the recurring webinar.', + }, + ], + }, + /* -------------------------------------------------------------------------- */ + /* webinar:getAll */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'User ID', + name: 'userId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'webinar', + ], + }, + }, + description: 'User ID or email-ID.', + }, + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'webinar', + ], + }, + }, + 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: [ + 'webinar', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 300, + }, + default: 30, + description: 'How many results to return.', + }, + /* -------------------------------------------------------------------------- */ + /* webinar:delete */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Webinar ID', + name: 'webinarId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'delete', + ], + resource: [ + 'webinarId', + ], + }, + }, + description: 'Webinar ID.', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + operation: [ + 'delete', + ], + resource: [ + 'webinar', + ], + }, + }, + options: [ + { + displayName: 'Occurrence ID', + name: 'occurrenceId', + type: 'string', + default: '', + description: 'Webinar occurrence ID.', + }, + + ], + + }, + /* -------------------------------------------------------------------------- */ + /* webinar:update */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Webinar ID', + name: 'webinarId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'update', + ], + resource: [ + 'webinar', + ], + }, + }, + description: 'User ID or email address of user.', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + operation: [ + 'update', + + ], + resource: [ + 'webinar', + ], + }, + }, + options: [ + { + displayName: 'Agenda', + name: 'agenda', + type: 'string', + default: '', + description: 'Webinar agenda.', + }, + { + displayName: 'Alternative Hosts', + name: 'alternativeHosts', + type: 'string', + default: '', + description: 'Alternative hosts email IDs.', + }, + { + displayName: 'Approval Type', + name: 'approvalType', + type: 'options', + options: [ + { + name: 'Automatically Approve', + value: 0, + }, + { + name: 'Manually Approve', + value: 1, + }, + { + name: 'No Registration Required', + value: 2, + }, + ], + default: 2, + description: 'Approval type.', + }, + { + displayName: 'Auto Recording', + name: 'autoRecording', + type: 'options', + options: [ + { + name: 'Record on Local', + value: 'local', + }, + { + name: 'Record on Cloud', + value: 'cloud', + }, + { + name: 'Disabled', + value: 'none', + }, + ], + default: 'none', + description: 'Auto recording.', + }, + { + displayName: 'Audio', + name: 'audio', + type: 'options', + options: [ + { + name: 'Both Telephony and VoiP', + value: 'both', + }, + { + name: 'Telephony', + value: 'telephony', + }, + { + name: 'VOIP', + value: 'voip', + }, + + ], + default: 'both', + description: 'Determine how participants can join audio portion of the webinar.', + }, + { + displayName: 'Duration', + name: 'duration', + type: 'string', + default: '', + description: 'Duration.', + }, + { + displayName: 'Host Video', + name: 'hostVideo', + type: 'boolean', + default: false, + description: 'Start video when host joins the webinar.', + }, + { + displayName: 'Occurrence ID', + name: 'occurrenceId', + type: 'string', + default: '', + description: `Webinar occurrence ID.`, + }, + { + displayName: 'Password', + name: 'password', + type: 'string', + default: '', + description: 'Password to join the webinar with maximum 10 characters.', + }, + { + displayName: 'Panelists Video', + name: 'panelistsVideo', + type: 'boolean', + default: false, + description: 'Start video when panelists joins the webinar.', + }, + { + displayName: 'Practice Session', + name: 'practiceSession', + type: 'boolean', + default: false, + description: 'Enable Practice session.', + }, + { + displayName: 'Registration Type', + name: 'registrationType', + type: 'options', + options: [ + { + name: 'Attendees register once and can attend any of the occurrences', + value: 1, + }, + { + name: 'Attendees need to register for every occurrence', + value: 2, + }, + { + name: 'Attendees register once and can choose one or more occurrences to attend', + value: 3, + }, + ], + default: 1, + description: 'Registration type. Used for recurring webinars with fixed time only.', + }, + { + displayName: 'Start Time', + name: 'startTime', + type: 'dateTime', + default: '', + description: 'Start time should be used only for scheduled or recurring webinar with fixed time.', + }, + { + displayName: 'Timezone', + name: 'timeZone', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getTimezones', + }, + default: '', + description: `Time zone used in the response. The default is the time zone of the calendar.`, + }, + { + displayName: 'Webinar Topic', + name: 'topic', + type: 'string', + default: '', + description: `Webinar topic.`, + }, + { + displayName: 'Webinar Type', + name: 'type', + type: 'options', + options: [ + { + name: 'Webinar', + value: 5, + }, + { + name: 'Recurring webinar with no fixed time', + value: 6, + }, + { + name: 'Recurring webinar with fixed time', + value: 9, + }, + ], + default: 5, + description: 'Webinar type.' + }, + ], + }, + +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Zoom/Zoom.node.ts b/packages/nodes-base/nodes/Zoom/Zoom.node.ts new file mode 100644 index 000000000..f7ecff0de --- /dev/null +++ b/packages/nodes-base/nodes/Zoom/Zoom.node.ts @@ -0,0 +1,821 @@ +import { + IExecuteFunctions, +} from 'n8n-core'; + +import { + IDataObject, + ILoadOptionsFunctions, + INodeExecutionData, + INodePropertyOptions, + INodeType, + INodeTypeDescription, +} from 'n8n-workflow'; + +import { + zoomApiRequest, + zoomApiRequestAllItems, +} from './GenericFunctions'; + +import { + meetingOperations, + meetingFields, +} from './MeetingDescription'; + +// import { +// meetingRegistrantOperations, +// meetingRegistrantFields, +// } from './MeetingRegistrantDescription'; + +// import { +// webinarOperations, +// webinarFields, +// } from './WebinarDescription'; + +import * as moment from 'moment-timezone'; + +interface Settings { + host_video?: boolean; + participant_video?: boolean; + panelists_video?: boolean; + cn_meeting?: boolean; + in_meeting?: boolean; + join_before_host?: boolean; + mute_upon_entry?: boolean; + watermark?: boolean; + waiting_room?: boolean; + audio?: string; + alternative_hosts?: string; + auto_recording?: string; + registration_type?: number; + approval_type?: number; + practice_session?: boolean; +} + +export class Zoom implements INodeType { + description: INodeTypeDescription = { + displayName: 'Zoom', + name: 'zoom', + group: ['input'], + version: 1, + description: 'Consume Zoom API', + subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', + defaults: { + name: 'Zoom', + color: '#0B6CF9' + }, + icon: 'file:zoom.png', + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + // create a JWT app on Zoom Marketplace + //https://marketplace.zoom.us/develop/create + //get the JWT token as access token + name: 'zoomApi', + required: true, + displayOptions: { + show: { + authentication: [ + 'accessToken', + ], + }, + }, + }, + { + //create a account level OAuth app + //https://marketplace.zoom.us/develop/create + name: 'zoomOAuth2Api', + required: true, + displayOptions: { + show: { + authentication: [ + 'oAuth2', + ], + }, + }, + }, + ], + properties: [ + { + displayName: 'Authentication', + name: 'authentication', + type: 'options', + options: [ + { + name: 'Access Token', + value: 'accessToken', + }, + { + name: 'OAuth2', + value: 'oAuth2', + }, + ], + default: 'accessToken', + description: 'The resource to operate on.', + }, + { + displayName: 'Resource', + name: 'resource', + type: 'options', + options: [ + { + name: 'Meeting', + value: 'meeting' + }, + // { + // name: 'Meeting Registrant', + // value: 'meetingRegistrant' + // }, + // { + // name: 'Webinar', + // value: 'webinar' + // } + ], + default: 'meeting', + description: 'The resource to operate on.' + }, + //MEETINGS + ...meetingOperations, + ...meetingFields, + + // //MEETING REGISTRANTS + // ...meetingRegistrantOperations, + // ...meetingRegistrantFields, + + // //WEBINARS + // ...webinarOperations, + // ...webinarFields, + ] + + }; + methods = { + loadOptions: { + // Get all the timezones to display them to user so that he can select them easily + async getTimezones( + this: ILoadOptionsFunctions + ): Promise { + const returnData: INodePropertyOptions[] = []; + for (const timezone of moment.tz.names()) { + const timezoneName = timezone; + const timezoneId = timezone; + returnData.push({ + name: timezoneName, + value: timezoneId + }); + } + return returnData; + } + } + }; + + async execute(this: IExecuteFunctions): Promise { + const items = this.getInputData(); + const returnData: IDataObject[] = []; + let 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 < items.length; i++) { + qs = {}; + //https://marketplace.zoom.us/docs/api-reference/zoom-api/ + if (resource === 'meeting') { + + if (operation === 'get') { + //https://marketplace.zoom.us/docs/api-reference/zoom-api/meetings/meeting + const meetingId = this.getNodeParameter('meetingId', i) as string; + const additionalFields = this.getNodeParameter( + 'additionalFields', + i + ) as IDataObject; + + if (additionalFields.showPreviousOccurrences) { + qs.show_previous_occurrences = additionalFields.showPreviousOccurrences as boolean; + } + + if (additionalFields.occurrenceId) { + qs.occurrence_id = additionalFields.occurrenceId as string; + } + + responseData = await zoomApiRequest.call( + this, + 'GET', + `/meetings/${meetingId}`, + {}, + qs + ); + } + if (operation === 'getAll') { + //https://marketplace.zoom.us/docs/api-reference/zoom-api/meetings/meetings + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + + const filters = this.getNodeParameter( + 'filters', + i + ) as IDataObject; + if (filters.type) { + qs.type = filters.type as string; + } + + if (returnAll) { + responseData = await zoomApiRequestAllItems.call(this, 'meetings', 'GET', '/users/me/meetings', {}, qs); + } else { + qs.page_size = this.getNodeParameter('limit', i) as number; + responseData = await zoomApiRequest.call(this, 'GET', '/users/me/meetings', {}, qs); + responseData = responseData.meetings; + } + + } + if (operation === 'delete') { + //https://marketplace.zoom.us/docs/api-reference/zoom-api/meetings/meetingdelete + const meetingId = this.getNodeParameter('meetingId', i) as string; + const additionalFields = this.getNodeParameter( + 'additionalFields', + i + ) as IDataObject; + if (additionalFields.scheduleForReminder) { + qs.schedule_for_reminder = additionalFields.scheduleForReminder as boolean; + } + + if (additionalFields.occurrenceId) { + qs.occurrence_id = additionalFields.occurrenceId; + } + + responseData = await zoomApiRequest.call( + this, + 'DELETE', + `/meetings/${meetingId}`, + {}, + qs + ); + responseData = { success: true }; + } + if (operation === 'create') { + //https://marketplace.zoom.us/docs/api-reference/zoom-api/meetings/meetingcreate + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + + const body: IDataObject = {}; + + if (additionalFields.settings) { + const settingValues: Settings = {}; + const settings = additionalFields.settings as IDataObject; + + if (settings.cnMeeting) { + settingValues.cn_meeting = settings.cnMeeting as boolean; + } + + if (settings.inMeeting) { + settingValues.in_meeting = settings.inMeeting as boolean; + } + + if (settings.joinBeforeHost) { + settingValues.join_before_host = settings.joinBeforeHost as boolean; + } + + if (settings.muteUponEntry) { + settingValues.mute_upon_entry = settings.muteUponEntry as boolean; + } + + if (settings.watermark) { + settingValues.watermark = settings.watermark as boolean; + } + + if (settings.audio) { + settingValues.audio = settings.audio as string; + } + + if (settings.alternativeHosts) { + settingValues.alternative_hosts = settings.alternativeHosts as string; + } + + if (settings.participantVideo) { + settingValues.participant_video = settings.participantVideo as boolean; + } + + if (settings.hostVideo) { + settingValues.host_video = settings.hostVideo as boolean; + } + + if (settings.autoRecording) { + settingValues.auto_recording = settings.autoRecording as string; + } + + if (settings.registrationType) { + settingValues.registration_type = settings.registrationType as number; + } + + body.settings = settingValues; + } + + body.topic = this.getNodeParameter('topic', i) as string; + + if (additionalFields.type) { + body.type = additionalFields.type as string; + } + + if (additionalFields.startTime) { + if (additionalFields.timeZone) { + body.start_time = moment(additionalFields.startTime as string).format('YYYY-MM-DDTHH:mm:ss'); + } else { + // if none timezone it's defined used n8n timezone + body.start_time = moment.tz(additionalFields.startTime as string, this.getTimezone()).format(); + } + } + + if (additionalFields.duration) { + body.duration = additionalFields.duration as number; + } + + if (additionalFields.scheduleFor) { + body.schedule_for = additionalFields.scheduleFor as string; + } + + if (additionalFields.timeZone) { + body.timezone = additionalFields.timeZone as string; + } + + if (additionalFields.password) { + body.password = additionalFields.password as string; + } + + if (additionalFields.agenda) { + body.agenda = additionalFields.agenda as string; + } + + responseData = await zoomApiRequest.call( + this, + 'POST', + `/users/me/meetings`, + body, + qs + ); + } + if (operation === 'update') { + //https://marketplace.zoom.us/docs/api-reference/zoom-api/meetings/meetingupdate + const meetingId = this.getNodeParameter('meetingId', i) as string; + const updateFields = this.getNodeParameter( + 'updateFields', + i + ) as IDataObject; + + const body: IDataObject = {}; + + if (updateFields.settings) { + const settingValues: Settings = {}; + const settings = updateFields.settings as IDataObject; + + if (settings.cnMeeting) { + settingValues.cn_meeting = settings.cnMeeting as boolean; + } + + if (settings.inMeeting) { + settingValues.in_meeting = settings.inMeeting as boolean; + } + + if (settings.joinBeforeHost) { + settingValues.join_before_host = settings.joinBeforeHost as boolean; + } + + if (settings.muteUponEntry) { + settingValues.mute_upon_entry = settings.muteUponEntry as boolean; + } + + if (settings.watermark) { + settingValues.watermark = settings.watermark as boolean; + } + + if (settings.audio) { + settingValues.audio = settings.audio as string; + } + + if (settings.alternativeHosts) { + settingValues.alternative_hosts = settings.alternativeHosts as string; + } + + if (settings.participantVideo) { + settingValues.participant_video = settings.participantVideo as boolean; + } + + if (settings.hostVideo) { + settingValues.host_video = settings.hostVideo as boolean; + } + + if (settings.autoRecording) { + settingValues.auto_recording = settings.autoRecording as string; + } + + if (settings.registrationType) { + settingValues.registration_type = settings.registrationType as number; + } + + body.settings = settingValues; + } + + if (updateFields.topic) { + body.topic = updateFields.topic as string; + } + + if (updateFields.type) { + body.type = updateFields.type as string; + } + + if (updateFields.startTime) { + body.start_time = updateFields.startTime as string; + } + + if (updateFields.duration) { + body.duration = updateFields.duration as number; + } + + if (updateFields.scheduleFor) { + body.schedule_for = updateFields.scheduleFor as string; + } + + if (updateFields.timeZone) { + body.timezone = updateFields.timeZone as string; + } + + if (updateFields.password) { + body.password = updateFields.password as string; + } + + if (updateFields.agenda) { + body.agenda = updateFields.agenda as string; + } + + responseData = await zoomApiRequest.call( + this, + 'PATCH', + `/meetings/${meetingId}`, + body, + qs + ); + + responseData = { success: true }; + + } + } + // if (resource === 'meetingRegistrant') { + // if (operation === 'create') { + // //https://marketplace.zoom.us/docs/api-reference/zoom-api/meetings/meetingregistrantcreate + // const meetingId = this.getNodeParameter('meetingId', i) as string; + // const emailId = this.getNodeParameter('email', i) as string; + // body.email = emailId; + // const firstName = this.getNodeParameter('firstName', i) as string; + // body.first_name = firstName; + // const additionalFields = this.getNodeParameter( + // 'additionalFields', + // i + // ) as IDataObject; + // if (additionalFields.occurrenceId) { + // qs.occurrence_ids = additionalFields.occurrenceId as string; + // } + // if (additionalFields.lastName) { + // body.last_name = additionalFields.lastName as string; + // } + // if (additionalFields.address) { + // body.address = additionalFields.address as string; + // } + // if (additionalFields.city) { + // body.city = additionalFields.city as string; + // } + // if (additionalFields.state) { + // body.state = additionalFields.state as string; + // } + // if (additionalFields.country) { + // body.country = additionalFields.country as string; + // } + // if (additionalFields.zip) { + // body.zip = additionalFields.zip as string; + // } + // if (additionalFields.phone) { + // body.phone = additionalFields.phone as string; + // } + // if (additionalFields.comments) { + // body.comments = additionalFields.comments as string; + // } + // if (additionalFields.org) { + // body.org = additionalFields.org as string; + // } + // if (additionalFields.jobTitle) { + // body.job_title = additionalFields.jobTitle as string; + // } + // if (additionalFields.purchasingTimeFrame) { + // body.purchasing_time_frame = additionalFields.purchasingTimeFrame as string; + // } + // if (additionalFields.roleInPurchaseProcess) { + // body.role_in_purchase_process = additionalFields.roleInPurchaseProcess as string; + // } + // responseData = await zoomApiRequest.call( + // this, + // 'POST', + // `/meetings/${meetingId}/registrants`, + // body, + // qs + // ); + // } + // if (operation === 'getAll') { + // //https://marketplace.zoom.us/docs/api-reference/zoom-api/meetings/meetingregistrants + // const meetingId = this.getNodeParameter('meetingId', i) as string; + // const additionalFields = this.getNodeParameter( + // 'additionalFields', + // i + // ) as IDataObject; + // if (additionalFields.occurrenceId) { + // qs.occurrence_id = additionalFields.occurrenceId as string; + // } + // if (additionalFields.status) { + // qs.status = additionalFields.status as string; + // } + // const returnAll = this.getNodeParameter('returnAll', i) as boolean; + // if (returnAll) { + // responseData = await zoomApiRequestAllItems.call(this, 'results', 'GET', `/meetings/${meetingId}/registrants`, {}, qs); + // } else { + // qs.page_size = this.getNodeParameter('limit', i) as number; + // responseData = await zoomApiRequest.call(this, 'GET', `/meetings/${meetingId}/registrants`, {}, qs); + + // } + + // } + // if (operation === 'update') { + // //https://marketplace.zoom.us/docs/api-reference/zoom-api/meetings/meetingregistrantstatus + // const meetingId = this.getNodeParameter('meetingId', i) as string; + // const additionalFields = this.getNodeParameter( + // 'additionalFields', + // i + // ) as IDataObject; + // if (additionalFields.occurenceId) { + // qs.occurrence_id = additionalFields.occurrenceId as string; + // } + // if (additionalFields.action) { + // body.action = additionalFields.action as string; + // } + // responseData = await zoomApiRequest.call( + // this, + // 'PUT', + // `/meetings/${meetingId}/registrants/status`, + // body, + // qs + // ); + // } + // } + // if (resource === 'webinar') { + // if (operation === 'create') { + // //https://marketplace.zoom.us/docs/api-reference/zoom-api/webinars/webinarcreate + // const userId = this.getNodeParameter('userId', i) as string; + // const additionalFields = this.getNodeParameter( + // 'additionalFields', + // i + // ) as IDataObject; + // const settings: Settings = {}; + + + // if (additionalFields.audio) { + // settings.audio = additionalFields.audio as string; + + // } + + // if (additionalFields.alternativeHosts) { + // settings.alternative_hosts = additionalFields.alternativeHosts as string; + + // } + + // if (additionalFields.panelistsVideo) { + // settings.panelists_video = additionalFields.panelistsVideo as boolean; + + // } + // if (additionalFields.hostVideo) { + // settings.host_video = additionalFields.hostVideo as boolean; + + // } + // if (additionalFields.practiceSession) { + // settings.practice_session = additionalFields.practiceSession as boolean; + + // } + // if (additionalFields.autoRecording) { + // settings.auto_recording = additionalFields.autoRecording as string; + + // } + + // if (additionalFields.registrationType) { + // settings.registration_type = additionalFields.registrationType as number; + + // } + // if (additionalFields.approvalType) { + // settings.approval_type = additionalFields.approvalType as number; + + // } + + // body = { + // settings, + // }; + + // if (additionalFields.topic) { + // body.topic = additionalFields.topic as string; + + // } + + // if (additionalFields.type) { + // body.type = additionalFields.type as string; + + // } + + // if (additionalFields.startTime) { + // body.start_time = additionalFields.startTime as string; + + // } + + // if (additionalFields.duration) { + // body.duration = additionalFields.duration as number; + + // } + + + // if (additionalFields.timeZone) { + // body.timezone = additionalFields.timeZone as string; + + // } + + // if (additionalFields.password) { + // body.password = additionalFields.password as string; + + // } + + // if (additionalFields.agenda) { + // body.agenda = additionalFields.agenda as string; + + // } + // responseData = await zoomApiRequest.call( + // this, + // 'POST', + // `/users/${userId}/webinars`, + // body, + // qs + // ); + // } + // if (operation === 'get') { + // //https://marketplace.zoom.us/docs/api-reference/zoom-api/webinars/webinar + // const webinarId = this.getNodeParameter('webinarId', i) as string; + + // const additionalFields = this.getNodeParameter( + // 'additionalFields', + // i + // ) as IDataObject; + // if (additionalFields.showPreviousOccurrences) { + // qs.show_previous_occurrences = additionalFields.showPreviousOccurrences as boolean; + + // } + + // if (additionalFields.occurrenceId) { + // qs.occurrence_id = additionalFields.occurrenceId as string; + + // } + + // responseData = await zoomApiRequest.call( + // this, + // 'GET', + // `/webinars/${webinarId}`, + // {}, + // qs + // ); + // } + // if (operation === 'getAll') { + // //https://marketplace.zoom.us/docs/api-reference/zoom-api/webinars/webinars + // const userId = this.getNodeParameter('userId', i) as string; + // const returnAll = this.getNodeParameter('returnAll', i) as boolean; + // if (returnAll) { + // responseData = await zoomApiRequestAllItems.call(this, 'results', 'GET', `/users/${userId}/webinars`, {}, qs); + // } else { + // qs.page_size = this.getNodeParameter('limit', i) as number; + // responseData = await zoomApiRequest.call(this, 'GET', `/users/${userId}/webinars`, {}, qs); + + // } + // } + // if (operation === 'delete') { + // //https://marketplace.zoom.us/docs/api-reference/zoom-api/webinars/webinardelete + // const webinarId = this.getNodeParameter('webinarId', i) as string; + // const additionalFields = this.getNodeParameter( + // 'additionalFields', + // i + // ) as IDataObject; + + + // if (additionalFields.occurrenceId) { + // qs.occurrence_id = additionalFields.occurrenceId; + + // } + + // responseData = await zoomApiRequest.call( + // this, + // 'DELETE', + // `/webinars/${webinarId}`, + // {}, + // qs + // ); + // responseData = { success: true }; + // } + // if (operation === 'update') { + // //https://marketplace.zoom.us/docs/api-reference/zoom-api/webinars/webinarupdate + // const webinarId = this.getNodeParameter('webinarId', i) as string; + // const additionalFields = this.getNodeParameter( + // 'additionalFields', + // i + // ) as IDataObject; + // if (additionalFields.occurrenceId) { + // qs.occurrence_id = additionalFields.occurrenceId as string; + + // } + // const settings: Settings = {}; + // if (additionalFields.audio) { + // settings.audio = additionalFields.audio as string; + + // } + // if (additionalFields.alternativeHosts) { + // settings.alternative_hosts = additionalFields.alternativeHosts as string; + + // } + + // if (additionalFields.panelistsVideo) { + // settings.panelists_video = additionalFields.panelistsVideo as boolean; + + // } + // if (additionalFields.hostVideo) { + // settings.host_video = additionalFields.hostVideo as boolean; + + // } + // if (additionalFields.practiceSession) { + // settings.practice_session = additionalFields.practiceSession as boolean; + + // } + // if (additionalFields.autoRecording) { + // settings.auto_recording = additionalFields.autoRecording as string; + + // } + + // if (additionalFields.registrationType) { + // settings.registration_type = additionalFields.registrationType as number; + + // } + // if (additionalFields.approvalType) { + // settings.approval_type = additionalFields.approvalType as number; + + // } + + // body = { + // settings, + // }; + + // if (additionalFields.topic) { + // body.topic = additionalFields.topic as string; + + // } + + // if (additionalFields.type) { + // body.type = additionalFields.type as string; + + // } + + // if (additionalFields.startTime) { + // body.start_time = additionalFields.startTime as string; + + // } + + // if (additionalFields.duration) { + // body.duration = additionalFields.duration as number; + + // } + + + // if (additionalFields.timeZone) { + // body.timezone = additionalFields.timeZone as string; + + // } + + // if (additionalFields.password) { + // body.password = additionalFields.password as string; + + // } + + // if (additionalFields.agenda) { + // body.agenda = additionalFields.agenda as string; + + // } + // responseData = await zoomApiRequest.call( + // this, + // 'PATCH', + // `webinars/${webinarId}`, + // body, + // qs + // ); + // } + // } + } + if (Array.isArray(responseData)) { + returnData.push.apply(returnData, responseData as IDataObject[]); + } else { + returnData.push(responseData as IDataObject); + } + + return [this.helpers.returnJsonArray(returnData)]; + } +} diff --git a/packages/nodes-base/nodes/Zoom/zoom.png b/packages/nodes-base/nodes/Zoom/zoom.png new file mode 100644 index 000000000..dfc72331e Binary files /dev/null and b/packages/nodes-base/nodes/Zoom/zoom.png differ diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 86980d1ef..cfb751d2b 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -142,6 +142,8 @@ "dist/credentials/ZendeskApi.credentials.js", "dist/credentials/ZendeskOAuth2Api.credentials.js", "dist/credentials/ZohoOAuth2Api.credentials.js", + "dist/credentials/ZoomApi.credentials.js", + "dist/credentials/ZoomOAuth2Api.credentials.js", "dist/credentials/ZulipApi.credentials.js" ], "nodes": [ @@ -298,6 +300,7 @@ "dist/nodes/Zendesk/Zendesk.node.js", "dist/nodes/Zendesk/ZendeskTrigger.node.js", "dist/nodes/Zoho/ZohoCrm.node.js", + "dist/nodes/Zoom/Zoom.node.js", "dist/nodes/Zulip/Zulip.node.js" ] },