Merge remote-tracking branch 'MLH-Fellowship/promptSave' into save-changes-warning

Merging contributor work
This commit is contained in:
Rupenieks
2020-08-31 10:45:25 +02:00
67 changed files with 3262 additions and 1711 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "n8n",
"version": "0.71.0",
"version": "0.72.0",
"description": "n8n Workflow Automation Tool",
"license": "SEE LICENSE IN LICENSE.md",
"homepage": "https://n8n.io",
@@ -100,9 +100,9 @@
"lodash.get": "^4.4.2",
"mongodb": "^3.5.5",
"mysql2": "^2.0.1",
"n8n-core": "~0.36.0",
"n8n-core": "~0.37.0",
"n8n-editor-ui": "~0.48.0",
"n8n-nodes-base": "~0.66.0",
"n8n-nodes-base": "~0.67.0",
"n8n-workflow": "~0.33.0",
"oauth-1.0a": "^2.2.6",
"open": "^7.0.0",

View File

@@ -316,14 +316,14 @@ export async function executeWorkflow(workflowInfo: IExecuteWorkflowInfo, additi
// Does not get used so set it simply to empty string
const executionId = '';
// Create new additionalData to have different workflow loaded and to call
// different webooks
const additionalDataIntegrated = await getBase(additionalData.credentials);
additionalDataIntegrated.hooks = getWorkflowHooksIntegrated(mode, executionId, workflowData!, { parentProcessMode: additionalData.hooks!.mode });
// Get the needed credentials for the current workflow as they will differ to the ones of the
// calling workflow.
additionalDataIntegrated.credentials = await WorkflowCredentials(workflowData!.nodes);
const credentials = await WorkflowCredentials(workflowData!.nodes);
// Create new additionalData to have different workflow loaded and to call
// different webooks
const additionalDataIntegrated = await getBase(credentials);
additionalDataIntegrated.hooks = getWorkflowHooksIntegrated(mode, executionId, workflowData!, { parentProcessMode: additionalData.hooks!.mode });
// Find Start-Node
const requiredNodeTypes = ['n8n-nodes-base.start'];

View File

@@ -1,6 +1,6 @@
{
"name": "n8n-core",
"version": "0.36.0",
"version": "0.37.0",
"description": "Core functionality of n8n",
"license": "SEE LICENSE IN LICENSE.md",
"homepage": "https://n8n.io",
@@ -45,7 +45,7 @@
"crypto-js": "3.1.9-1",
"lodash.get": "^4.4.2",
"mmmagic": "^0.5.2",
"n8n-workflow": "~0.32.0",
"n8n-workflow": "~0.33.0",
"p-cancelable": "^2.0.0",
"request": "^2.88.2",
"request-promise-native": "^1.0.7"

View File

@@ -41,8 +41,13 @@ export async function prepareUserSettings(): Promise<IUserSettings> {
userSettings = {};
}
// Settings and/or key do not exist. So generate a new encryption key
userSettings.encryptionKey = randomBytes(24).toString('base64');
if (process.env[ENCRYPTION_KEY_ENV_OVERWRITE] !== undefined) {
// Use the encryption key which got set via environment
userSettings.encryptionKey = process.env[ENCRYPTION_KEY_ENV_OVERWRITE];
} else {
// Generate a new encryption key
userSettings.encryptionKey = randomBytes(24).toString('base64');
}
console.log(`UserSettings got generated and saved to: ${settingsPath}`);

View File

@@ -459,7 +459,7 @@ export class WorkflowExecute {
let executionData: IExecuteData;
let executionError: IExecutionError | undefined;
let executionNode: INode;
let nodeSuccessData: INodeExecutionData[][] | null;
let nodeSuccessData: INodeExecutionData[][] | null | undefined;
let runIndex: number;
let startTime: number;
let taskData: ITaskData;
@@ -593,9 +593,15 @@ export class WorkflowExecute {
}
}
this.runExecutionData.resultData.lastNodeExecuted = executionData.node.name;
nodeSuccessData = await workflow.runNode(executionData.node, executionData.data, this.runExecutionData, runIndex, this.additionalData, NodeExecuteFunctions, this.mode);
if (nodeSuccessData === undefined) {
// Node did not get executed
nodeSuccessData = null;
} else {
this.runExecutionData.resultData.lastNodeExecuted = executionData.node.name;
}
if (nodeSuccessData === null || nodeSuccessData[0][0] === undefined) {
if (executionData.node.alwaysOutputData === true) {
nodeSuccessData = nodeSuccessData || [];

View File

@@ -435,21 +435,38 @@ export default mixins(
saveAs(blob, workflowName + '.json');
} else if (key === 'workflow-save') {
console.log("saving......");
this.saveCurrentWorkflow();
} else if (key === 'workflow-save-as') {
console.log("saving......");
this.saveCurrentWorkflow(true);
} else if (key === 'help-about') {
this.aboutDialogVisible = true;
} else if (key === 'workflow-settings') {
this.workflowSettingsDialogVisible = true;
} else if (key === 'workflow-new') {
this.$router.push({ name: 'NodeViewNew' });
const workflowId = this.$store.getters.workflowId;
const result = await this.dataHasChanged(workflowId);
if(result) {
const importConfirm = await this.confirmMessage(`When you switch workflows your current workflow changes will be lost.`, 'Save your Changes?', 'warning', 'Yes, switch workflows and forget changes');
if (importConfirm === true) {
this.$router.push({ name: 'NodeViewNew' });
this.$showMessage({
title: 'Workflow created',
message: 'A new workflow got created!',
type: 'success',
});
this.$showMessage({
title: 'Workflow created',
message: 'A new workflow got created!',
type: 'success',
});
}
} else {
this.$router.push({ name: 'NodeViewNew' });
this.$showMessage({
title: 'Workflow created',
message: 'A new workflow got created!',
type: 'success',
});
}
} else if (key === 'credentials-open') {
this.credentialOpenDialogVisible = true;
} else if (key === 'credentials-new') {

View File

@@ -33,6 +33,7 @@ import WorkflowActivator from '@/components/WorkflowActivator.vue';
import { restApi } from '@/components/mixins/restApi';
import { genericHelpers } from '@/components/mixins/genericHelpers';
import { workflowHelpers } from '@/components/mixins/workflowHelpers';
import { showMessage } from '@/components/mixins/showMessage';
import { IWorkflowShortResponse } from '@/Interface';
@@ -42,6 +43,7 @@ export default mixins(
genericHelpers,
restApi,
showMessage,
workflowHelpers,
).extend({
name: 'WorkflowOpen',
props: [
@@ -87,9 +89,20 @@ export default mixins(
this.$emit('closeDialog');
return false;
},
openWorkflow (data: IWorkflowShortResponse, column: any) { // tslint:disable-line:no-any
async openWorkflow (data: IWorkflowShortResponse, column: any) { // tslint:disable-line:no-any
if (column.label !== 'Active') {
this.$emit('openWorkflow', data.id);
const workflowId = this.$store.getters.workflowId;
const result = await this.dataHasChanged(workflowId);
if(result) {
const importConfirm = await this.confirmMessage(`When you switch workflows your current workflow changes will be lost.`, 'Save your Changes?', 'warning', 'Yes, switch workflows and forget changes');
if (importConfirm === false) {
return;
} else {
this.$emit('openWorkflow', data.id);
}
} else {
this.$emit('openWorkflow', data.id);
}
}
},
openDialog () {

View File

@@ -22,6 +22,7 @@ import {
INodeTypesMaxCount,
INodeUi,
IWorkflowData,
IWorkflowDb,
IWorkflowDataUpdate,
XYPositon,
} from '../../Interface';
@@ -30,6 +31,8 @@ import { restApi } from '@/components/mixins/restApi';
import { nodeHelpers } from '@/components/mixins/nodeHelpers';
import { showMessage } from '@/components/mixins/showMessage';
import { isEqual } from 'lodash';
import mixins from 'vue-typed-mixins';
export const workflowHelpers = mixins(
@@ -478,5 +481,29 @@ export const workflowHelpers = mixins(
node.position[1] += offsetPosition[1];
}
},
async dataHasChanged(id: string) {
const currentData = await this.getWorkflowDataToSave();
let data: IWorkflowDb;
data = await this.restApi().getWorkflow(id);
if(data !== undefined) {
const x = {
nodes: data.nodes,
connections: data.connections,
settings: data.settings,
name: data.name
};
const y = {
nodes: currentData.nodes,
connections: currentData.connections,
settings: currentData.settings,
name: currentData.name
};
return !isEqual(x, y);
}
return true;
},
},
});

View File

@@ -126,7 +126,7 @@ import RunData from '@/components/RunData.vue';
import mixins from 'vue-typed-mixins';
import { debounce } from 'lodash';
import { debounce, isEqual } from 'lodash';
import axios from 'axios';
import {
IConnection,
@@ -186,6 +186,36 @@ export default mixins(
// When a node gets set as active deactivate the create-menu
this.createNodeActive = false;
},
nodes: {
async handler (val, oldVal) {
// Load a workflow
let workflowId = null as string | null;
if (this.$route && this.$route.params.name) {
workflowId = this.$route.params.name;
}
if(workflowId !== null) {
this.isDirty = await this.dataHasChanged(workflowId);
} else {
this.isDirty = true;
}
},
deep: true
},
connections: {
async handler (val, oldVal) {
// Load a workflow
let workflowId = null as string | null;
if (this.$route && this.$route.params.name) {
workflowId = this.$route.params.name;
}
if(workflowId !== null) {
this.isDirty = await this.dataHasChanged(workflowId);
} else {
this.isDirty = true;
}
},
deep: true
},
},
computed: {
activeNode (): INodeUi | null {
@@ -259,6 +289,7 @@ export default mixins(
ctrlKeyPressed: false,
debouncedFunctions: [] as any[], // tslint:disable-line:no-any
stopExecutionInProgress: false,
isDirty: false,
};
},
beforeDestroy () {
@@ -330,6 +361,8 @@ export default mixins(
this.$store.commit('setWorkflowSettings', data.settings || {});
await this.addNodes(data.nodes, data.connections);
return data;
},
mouseDown (e: MouseEvent) {
// Save the location of the mouse click
@@ -431,6 +464,8 @@ export default mixins(
e.stopPropagation();
e.preventDefault();
this.isDirty = false;
this.callDebounced('saveCurrentWorkflow', 1000);
} else if (e.key === 'Enter') {
// Activate the last selected node
@@ -1303,12 +1338,14 @@ export default mixins(
if (this.$route.params.action === 'workflowSave') {
// In case the workflow got saved we do not have to run init
// as only the route changed but all the needed data is already loaded
this.isDirty = false;
return Promise.resolve();
}
if (this.$route.name === 'ExecutionById') {
// Load an execution
const executionId = this.$route.params.id;
await this.openExecution(executionId);
} else {
// Load a workflow
@@ -1316,7 +1353,6 @@ export default mixins(
if (this.$route.params.name) {
workflowId = this.$route.params.name;
}
if (workflowId !== null) {
// Open existing workflow
await this.openWorkflow(workflowId);
@@ -1328,6 +1364,17 @@ export default mixins(
document.addEventListener('keydown', this.keyDown);
document.addEventListener('keyup', this.keyUp);
window.addEventListener("beforeunload", (e) => {
if(this.isDirty === true) {
const confirmationMessage = 'It looks like you have been editing something. '
+ 'If you leave before saving, your changes will be lost.';
(e || window.event).returnValue = confirmationMessage; //Gecko + IE
return confirmationMessage; //Gecko + Webkit, Safari, Chrome etc.
} else {
return;
}
});
},
__addConnection (connection: [IConnection, IConnection], addVisualConnection = false) {
if (addVisualConnection === true) {
@@ -1872,13 +1919,13 @@ export default mixins(
async mounted () {
this.$root.$on('importWorkflowData', async (data: IDataObject) => {
await this.importWorkflowData(data.data as IWorkflowDataUpdate);
const resData = await this.importWorkflowData(data.data as IWorkflowDataUpdate);
});
this.$root.$on('importWorkflowUrl', async (data: IDataObject) => {
const workflowData = await this.getWorkflowDataFromUrl(data.url as string);
if (workflowData !== undefined) {
await this.importWorkflowData(workflowData);
const resData = await this.importWorkflowData(workflowData);
}
});

View File

@@ -127,7 +127,7 @@ export class MyNode implements INodeType {
The "description" property has to be set on all nodes because it contains all
the base information. Additionally do all nodes have to have exactly one of the
following methods defined which contains the the actual logic:
following methods defined which contains the actual logic:
**Regular node**
@@ -138,8 +138,8 @@ Method get called when the workflow gets executed
By default always `execute` should be used especially when creating a
third-party integration. The reason for that is that it is way more flexible
and allows to, for example, return a different amount of items than it received
as input. This is very important when a node should query data like return
all users. In that case, does the node normally just receive one input-item
as input. This is very important when a node should query data like *return
all users*. In that case, does the node normally just receive one input-item
but returns as many as users exist. So in doubt always `execute` should be
used!
@@ -188,10 +188,10 @@ The following properties can be set in the node description:
- **outputs** [required]: Types of outputs the node has (currently only "main" exists) and the amount
- **outputNames** [optional]: In case a node has multiple outputs names can be set that users know what data to expect
- **maxNodes** [optional]: If not an unlimited amount of nodes of that type can exist in a workflow the max-amount can be specified
- **name** [required]: Nme of the node (for n8n to use internally in camelCase)
- **name** [required]: Name of the node (for n8n to use internally, in camelCase)
- **properties** [required]: Properties which get displayed in the Editor UI and can be set by the user
- **subtitle** [optional]: Text which should be displayed underneath the name of the node in the Editor UI (can be an expression)
- **version** [required]: Version of the node. Currently always "1" (integer). For future usage does not get used yet.
- **version** [required]: Version of the node. Currently always "1" (integer). For future usage, does not get used yet.
- **webhooks** [optional]: Webhooks the node should listen to
@@ -200,12 +200,12 @@ The following properties can be set in the node description:
The following properties can be set in the node properties:
- **default** [required]: Default value of the property
- **description** [required]: Description to display users in Editor UI
- **displayName** [required]: Name to display users in Editor UI
- **description** [required]: Description that is displayed to users in the Editor UI
- **displayName** [required]: Name that is displayed to users in the Editor UI
- **displayOptions** [optional]: Defines logic to decide if a property should be displayed or not
- **name** [required]: Name of the property (for n8n to use internally in camelCase)
- **name** [required]: Name of the property (for n8n to use internally, in camelCase)
- **options** [optional]: The options the user can select when type of property is "collection", "fixedCollection" or "options"
- **placeholder** [optional]: Placeholder text to display users in Editor UI
- **placeholder** [optional]: Placeholder text that is displayed to users in the Editor UI
- **type** [required]: Type of the property. If it is for example a "string", "number", ...
- **typeOptions** [optional]: Additional options for type. Like for example the min or max value of a number
- **required** [optional]: Defines if the value has to be set or if it can stay empty
@@ -215,11 +215,11 @@ The following properties can be set in the node properties:
The following properties can be set in the node property options.
All properties are optional. The most, however, work only work when the node-property is of a specfic type.
All properties are optional. However, most only work when the node-property is of a specfic type.
- **alwaysOpenEditWindow** [type: string]: If set then the "Editor Window" will always open when the user tries to edit the field. Is helpful when long texts normally get used in the property
- **alwaysOpenEditWindow** [type: string]: If set then the "Editor Window" will always open when the user tries to edit the field. Helpful if long text is typically used in the property.
- **loadOptionsMethod** [type: options]: Method to use to load options from an external service
- **maxValue** [type: number]: Maximal value of the number
- **maxValue** [type: number]: Maximum value of the number
- **minValue** [type: number]: Minimum value of the number
- **multipleValues** [type: all]: If set the property gets turned into an Array and the user can add multiple values
- **multipleValueButtonText** [type: all]: Custom text for add button in case "multipleValues" got set

View File

@@ -27,7 +27,7 @@ export class ClassNameReplace implements INodeType {
{
name: 'default',
httpMethod: 'POST',
reponseMode: 'onReceived',
responseMode: 'onReceived',
// Each webhook property can either be hardcoded
// like the above ones or referenced from a parameter
// like the "path" property bellow

View File

@@ -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: ''
}
];
}

View File

@@ -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'
}
];
}

View File

@@ -1,7 +1,7 @@
const { src, dest } = require('gulp');
function copyIcons() {
return src('nodes/**/*.png')
return src('nodes/**/*.{png,svg}')
.pipe(dest('dist/nodes'));
}

View File

@@ -243,9 +243,10 @@ export class DateTime implements INodeType {
if (currentDate === undefined) {
continue;
}
if (!moment(currentDate as string | number).isValid()) {
if (options.fromFormat === undefined && !moment(currentDate as string | number).isValid()) {
throw new Error('The date input format could not be recognized. Please set the "From Format" field');
}
if (Number.isInteger(currentDate as unknown as number)) {
newDate = moment.unix(currentDate as unknown as number);
} else {

View File

@@ -1,6 +1,6 @@
import {
IExecuteFunctions,
} from 'n8n-core';
} from 'n8n-core';
import {
IDataObject,
@@ -102,6 +102,7 @@ export class GoogleTasks implements INodeType {
body = {};
//https://developers.google.com/tasks/v1/reference/tasks/insert
const taskId = this.getNodeParameter('task', i) as string;
body.title = this.getNodeParameter('title', i) as string;
const additionalFields = this.getNodeParameter(
'additionalFields',
i
@@ -121,11 +122,6 @@ export class GoogleTasks implements INodeType {
if (additionalFields.notes) {
body.notes = additionalFields.notes as string;
}
if (additionalFields.title) {
body.title = additionalFields.title as string;
}
if (additionalFields.dueDate) {
body.dueDate = additionalFields.dueDate as string;
}

View File

@@ -70,6 +70,13 @@ export const taskFields = [
},
default: '',
},
{
displayName: 'Title',
name: 'title',
type: 'string',
default: '',
description: 'Title of the task.',
},
{
displayName: 'Additional Fields',
name: 'additionalFields',
@@ -146,13 +153,7 @@ export const taskFields = [
default: '',
description: 'Current status of the task.',
},
{
displayName: 'Title',
name: 'title',
type: 'string',
default: '',
description: 'Title of the task.',
},
],
},
/* -------------------------------------------------------------------------- */

View File

@@ -402,6 +402,7 @@ export class Redis implements INodeType {
} else if (type === 'hash') {
const clientHset = util.promisify(client.hset).bind(client);
for (const key of Object.keys(value)) {
// @ts-ignore
await clientHset(keyName, key, (value as IDataObject)[key]!.toString());
}
} else if (type === 'list') {

View File

@@ -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<any> { // tslint:disable-line:no-any
export async function salesforceApiRequestAllItems(this: IExecuteFunctions | ILoadOptionsFunctions, propertyName: string, method: string, endpoint: string, body: any = {}, query: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
const returnData: IDataObject[] = [];

View File

@@ -91,7 +91,7 @@ export const messageFields = [
],
},
},
description: 'Post the message as authenticated user instead of bot.',
description: 'Post the message as authenticated user instead of bot. Works only with user token.',
},
{
displayName: 'User Name',
@@ -486,6 +486,26 @@ export const messageFields = [
},
description: `Timestamp of the message to be updated.`,
},
{
displayName: 'As User',
name: 'as_user',
type: 'boolean',
default: false,
displayOptions: {
show: {
authentication: [
'accessToken',
],
operation: [
'update'
],
resource: [
'message',
],
},
},
description: 'Pass true to update the message as the authed user. Works only with user token.',
},
{
displayName: 'Update Fields',
name: 'updateFields',

View File

@@ -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
@@ -452,15 +452,12 @@ export class Slack implements INodeType {
}
if (body.as_user === false) {
body.username = this.getNodeParameter('username', i) as string;
delete body.as_user;
}
// ignore body.as_user as it's deprecated
delete body.as_user;
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
@@ -486,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 = {};
@@ -502,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;
}
@@ -512,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 = {
@@ -552,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') {
@@ -573,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) {
@@ -593,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') {
@@ -612,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;
@@ -790,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 = {
@@ -804,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;

View File

@@ -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<any> { // 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<any> {
// 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);
});
}

View File

@@ -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[];

View File

@@ -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[];

View File

@@ -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[];

View File

@@ -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<INodePropertyOptions[]> {
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<INodeExecutionData[][]> {
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)];
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -1,6 +1,6 @@
{
"name": "n8n-nodes-base",
"version": "0.66.0",
"version": "0.67.0",
"description": "Base nodes of n8n",
"license": "SEE LICENSE IN LICENSE.md",
"homepage": "https://n8n.io",
@@ -143,6 +143,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": [
@@ -300,6 +302,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"
]
},
@@ -350,7 +353,7 @@
"moment-timezone": "^0.5.28",
"mongodb": "^3.5.5",
"mysql2": "^2.0.1",
"n8n-core": "~0.36.0",
"n8n-core": "~0.37.0",
"nodemailer": "^6.4.6",
"pdf-parse": "^1.1.1",
"pg-promise": "^9.0.3",

View File

@@ -1085,18 +1085,18 @@ export class Workflow {
* @returns {(Promise<INodeExecutionData[][] | null>)}
* @memberof Workflow
*/
async runNode(node: INode, inputData: ITaskDataConnections, runExecutionData: IRunExecutionData, runIndex: number, additionalData: IWorkflowExecuteAdditionalData, nodeExecuteFunctions: INodeExecuteFunctions, mode: WorkflowExecuteMode): Promise<INodeExecutionData[][] | null> {
async runNode(node: INode, inputData: ITaskDataConnections, runExecutionData: IRunExecutionData, runIndex: number, additionalData: IWorkflowExecuteAdditionalData, nodeExecuteFunctions: INodeExecuteFunctions, mode: WorkflowExecuteMode): Promise<INodeExecutionData[][] | null | undefined> {
if (node.disabled === true) {
// If node is disabled simply pass the data through
// return NodeRunHelpers.
if (inputData.hasOwnProperty('main') && inputData.main.length > 0) {
// If the node is disabled simply return the data from the first main input
if (inputData.main[0] === null) {
return null;
return undefined;
}
return [(inputData.main[0] as INodeExecutionData[])];
}
return null;
return undefined;
}
const nodeType = this.nodeTypes.getByName(node.type);
@@ -1112,7 +1112,7 @@ export class Workflow {
if (connectionInputData.length === 0) {
// No data for node so return
return null;
return undefined;
}
if (runExecutionData.resultData.lastNodeExecuted === node.name && runExecutionData.resultData.error !== undefined) {