diff --git a/packages/nodes-base/credentials/ZoomApi.credentials.ts b/packages/nodes-base/credentials/ZoomApi.credentials.ts new file mode 100644 index 0000000000..3db4aadbe0 --- /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: 'Access 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 0000000000..2b05c819a7 --- /dev/null +++ b/packages/nodes-base/credentials/ZoomOAuth2Api.credentials.ts @@ -0,0 +1,50 @@ +import { ICredentialType, NodePropertyTypes } from 'n8n-workflow'; + +const userScopes = [ + 'meeting:read', + 'meeting:write', + 'user:read', + 'user:write', + 'user_profile', + 'webinar:read', + 'webinar:write' +]; + +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: 'body' + } + ]; +} diff --git a/packages/nodes-base/nodes/Zoom/GenericFunctions.ts b/packages/nodes-base/nodes/Zoom/GenericFunctions.ts new file mode 100644 index 0000000000..0e2feda8db --- /dev/null +++ b/packages/nodes-base/nodes/Zoom/GenericFunctions.ts @@ -0,0 +1,128 @@ +import { OptionsWithUri } from 'request'; + +import { + IExecuteFunctions, + IExecuteSingleFunctions, + ILoadOptionsFunctions +} from 'n8n-core'; + +import { IDataObject } from 'n8n-workflow'; +import * as _ from 'lodash'; + +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://zoom.us/oauth${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 = 1; + query.count = 100; + do { + responseData = await zoomApiRequest.call( + this, + method, + endpoint, + body, + query + ); + query.cursor = encodeURIComponent( + _.get(responseData, 'response_metadata.next_cursor') + ); + query.page++; + returnData.push.apply(returnData, responseData[propertyName]); + } while ( + (responseData.response_metadata !== undefined && + responseData.response_metadata.mext_cursor !== undefined && + responseData.response_metadata.next_cursor !== '' && + responseData.response_metadata.next_cursor !== null) || + (responseData.paging !== undefined && + responseData.paging.pages !== undefined && + responseData.paging.page !== undefined && + responseData.paging.page < responseData.paging.pages) + ); + + return returnData; +} + +export function validateJSON(json: string | undefined): any { + // tslint:disable-line:no-any + let result; + try { + result = JSON.parse(json!); + } catch (exception) { + result = undefined; + } + return result; +} 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 0000000000..ce32b4c51b --- /dev/null +++ b/packages/nodes-base/nodes/Zoom/Zoom.node.ts @@ -0,0 +1,103 @@ +import { IExecuteFunctions } from 'n8n-core'; +import { + IDataObject, + INodeExecutionData, + INodeType, + INodeTypeDescription +} from 'n8n-workflow'; +import { + zoomApiRequest, + zoomApiRequestAllItems, + validateJSON +} from './GenericFunctions'; +export class Zoom implements INodeType { + description: INodeTypeDescription = { + displayName: 'Zoom', + name: 'zoom', + group: ['input'], + version: 1, + description: 'Consume Zoom API', + defaults: { + name: 'Zoom', + color: '#772244' + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'zoomApi', + required: true, + displayOptions: { + show: { + authentication: ['accessToken'] + } + } + }, + { + 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.' + } + ] + }; + + async execute(this: IExecuteFunctions): Promise { + const items = this.getInputData(); + const returnData: IDataObject[] = []; + const length = (items.length as unknown) as number; + let qs: IDataObject; + let responseData; + const authentication = this.getNodeParameter('authentication', 0) as string; + const resource = this.getNodeParameter('resource', 0) as string; + const operation = this.getNodeParameter('operation', 0) as string; + for (let i = 0; i < length; i++) { + qs = {}; + if (resource === 'channel') { + //https://api.slack.com/methods/conversations.archive + if (operation === 'archive') { + const channel = this.getNodeParameter('channelId', i) as string; + const body: IDataObject = { + channel + }; + responseData = await zoomApiRequest.call( + this, + 'POST', + '/conversations.archive', + 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 0000000000..dfc72331eb 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 a09d97d032..d5dc7a13dc 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -138,6 +138,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": [ @@ -292,6 +294,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" ] },