mirror of
https://github.com/n8n-io/n8n.git
synced 2024-11-10 06:34:05 -08:00
🔀 Merge branch 'master' into Fix-AMQP-Nodes
This commit is contained in:
commit
3e67617130
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "n8n",
|
||||
"version": "0.75.0",
|
||||
"version": "0.76.0",
|
||||
"description": "n8n Workflow Automation Tool",
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"homepage": "https://n8n.io",
|
||||
|
@ -100,10 +100,10 @@
|
|||
"lodash.get": "^4.4.2",
|
||||
"mongodb": "^3.5.5",
|
||||
"mysql2": "^2.0.1",
|
||||
"n8n-core": "~0.40.0",
|
||||
"n8n-editor-ui": "~0.51.0",
|
||||
"n8n-nodes-base": "~0.70.0",
|
||||
"n8n-workflow": "~0.36.0",
|
||||
"n8n-core": "~0.41.0",
|
||||
"n8n-editor-ui": "~0.52.0",
|
||||
"n8n-nodes-base": "~0.71.0",
|
||||
"n8n-workflow": "~0.37.0",
|
||||
"oauth-1.0a": "^2.2.6",
|
||||
"open": "^7.0.0",
|
||||
"pg": "^8.3.0",
|
||||
|
|
|
@ -1162,6 +1162,13 @@ class App {
|
|||
const authQueryParameters = _.get(oauthCredentials, 'authQueryParameters', '') as string;
|
||||
let returnUri = oAuthObj.code.getUri();
|
||||
|
||||
// if scope uses comma, change it as the library always return then with spaces
|
||||
if ((_.get(oauthCredentials, 'scope') as string).includes(',')) {
|
||||
const data = querystring.parse(returnUri.split('?')[1] as string);
|
||||
data.scope = _.get(oauthCredentials, 'scope') as string;
|
||||
returnUri = `${_.get(oauthCredentials, 'authUrl', '')}?${querystring.stringify(data)}`;
|
||||
}
|
||||
|
||||
if (authQueryParameters) {
|
||||
returnUri += '&' + authQueryParameters;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "n8n-core",
|
||||
"version": "0.40.0",
|
||||
"version": "0.41.0",
|
||||
"description": "Core functionality of n8n",
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"homepage": "https://n8n.io",
|
||||
|
@ -46,7 +46,7 @@
|
|||
"file-type": "^14.6.2",
|
||||
"lodash.get": "^4.4.2",
|
||||
"mime-types": "^2.1.27",
|
||||
"n8n-workflow": "~0.36.0",
|
||||
"n8n-workflow": "~0.37.0",
|
||||
"p-cancelable": "^2.0.0",
|
||||
"request": "^2.88.2",
|
||||
"request-promise-native": "^1.0.7"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "n8n-editor-ui",
|
||||
"version": "0.51.0",
|
||||
"version": "0.52.0",
|
||||
"description": "Workflow Editor UI for n8n",
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"homepage": "https://n8n.io",
|
||||
|
@ -66,7 +66,7 @@
|
|||
"lodash.debounce": "^4.0.8",
|
||||
"lodash.get": "^4.4.2",
|
||||
"lodash.set": "^4.3.2",
|
||||
"n8n-workflow": "~0.36.0",
|
||||
"n8n-workflow": "~0.37.0",
|
||||
"node-sass": "^4.12.0",
|
||||
"prismjs": "^1.17.1",
|
||||
"quill": "^2.0.0-dev.3",
|
||||
|
|
19
packages/nodes-base/credentials/CustomerIoApi.credentials.ts
Normal file
19
packages/nodes-base/credentials/CustomerIoApi.credentials.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
import {
|
||||
ICredentialType,
|
||||
NodePropertyTypes,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
|
||||
export class CustomerIoApi implements ICredentialType {
|
||||
name = 'customerIoApi';
|
||||
displayName = 'Customer.io API';
|
||||
properties = [
|
||||
{
|
||||
displayName: 'App API Key',
|
||||
name: 'apiKey',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
default: '',
|
||||
},
|
||||
|
||||
];
|
||||
}
|
17
packages/nodes-base/credentials/MediumApi.credentials.ts
Normal file
17
packages/nodes-base/credentials/MediumApi.credentials.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
import {
|
||||
ICredentialType,
|
||||
NodePropertyTypes,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export class MediumApi implements ICredentialType {
|
||||
name = 'mediumApi';
|
||||
displayName = 'Medium API';
|
||||
properties = [
|
||||
{
|
||||
displayName: 'Access Token',
|
||||
name: 'accessToken',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
default: '',
|
||||
},
|
||||
];
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
import {
|
||||
ICredentialType,
|
||||
NodePropertyTypes,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export class MediumOAuth2Api implements ICredentialType {
|
||||
name = 'mediumOAuth2Api';
|
||||
extends = [
|
||||
'oAuth2Api',
|
||||
];
|
||||
displayName = 'Medium OAuth2 API';
|
||||
properties = [
|
||||
{
|
||||
displayName: 'Authorization URL',
|
||||
name: 'authUrl',
|
||||
type: 'hidden' as NodePropertyTypes,
|
||||
default: 'https://medium.com/m/oauth/authorize',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Access Token URL',
|
||||
name: 'accessTokenUrl',
|
||||
type: 'hidden' as NodePropertyTypes,
|
||||
default: 'https://medium.com/v1/tokens',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Scope',
|
||||
name: 'scope',
|
||||
type: 'hidden' as NodePropertyTypes,
|
||||
default: 'basicProfile,publishPost,listPublications',
|
||||
},
|
||||
{
|
||||
displayName: 'Client ID',
|
||||
name: 'clientId',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
default: '',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Client Secret',
|
||||
name: 'clientSecret',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
default: '',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Auth URI Query Parameters',
|
||||
name: 'authQueryParameters',
|
||||
type: 'hidden' as NodePropertyTypes,
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Authentication',
|
||||
name: 'authentication',
|
||||
type: 'hidden' as NodePropertyTypes,
|
||||
default: 'body',
|
||||
},
|
||||
];
|
||||
}
|
17
packages/nodes-base/credentials/TravisCiApi.credentials.ts
Normal file
17
packages/nodes-base/credentials/TravisCiApi.credentials.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
import {
|
||||
ICredentialType,
|
||||
NodePropertyTypes,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export class TravisCiApi implements ICredentialType {
|
||||
name = 'travisCiApi';
|
||||
displayName = 'Travis API';
|
||||
properties = [
|
||||
{
|
||||
displayName: 'API Token',
|
||||
name: 'apiToken',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
default: '',
|
||||
},
|
||||
];
|
||||
}
|
17
packages/nodes-base/credentials/TwakeCloudApi.credentials.ts
Normal file
17
packages/nodes-base/credentials/TwakeCloudApi.credentials.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
import {
|
||||
ICredentialType,
|
||||
NodePropertyTypes,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export class TwakeCloudApi implements ICredentialType {
|
||||
name = 'twakeCloudApi';
|
||||
displayName = 'Twake API';
|
||||
properties = [
|
||||
{
|
||||
displayName: 'Workspace Key',
|
||||
name: 'workspaceKey',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
default: '',
|
||||
},
|
||||
];
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
import {
|
||||
ICredentialType,
|
||||
NodePropertyTypes,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export class TwakeServerApi implements ICredentialType {
|
||||
name = 'twakeServerApi';
|
||||
displayName = 'Twake API';
|
||||
properties = [
|
||||
{
|
||||
displayName: 'Host URL',
|
||||
name: 'hostUrl',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Public ID',
|
||||
name: 'publicId',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Private API Key',
|
||||
name: 'privateApiKey',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
default: '',
|
||||
},
|
||||
];
|
||||
}
|
|
@ -19,8 +19,8 @@ export async function awsApiRequest(this: IHookFunctions | IExecuteFunctions | I
|
|||
const endpoint = `${service}.${credentials.region}.amazonaws.com`;
|
||||
|
||||
// Sign AWS API request with the user credentials
|
||||
const signOpts = {headers: headers || {}, host: endpoint, method, path, body};
|
||||
sign(signOpts, {accessKeyId: `${credentials.accessKeyId}`, secretAccessKey: `${credentials.secretAccessKey}`});
|
||||
const signOpts = { headers: headers || {}, host: endpoint, method, path, body };
|
||||
sign(signOpts, { accessKeyId: `${credentials.accessKeyId}`, secretAccessKey: `${credentials.secretAccessKey}` });
|
||||
|
||||
const options: OptionsWithUri = {
|
||||
headers: signOpts.headers,
|
||||
|
|
329
packages/nodes-base/nodes/CustomerIo/CustomerIoTrigger.node.ts
Normal file
329
packages/nodes-base/nodes/CustomerIo/CustomerIoTrigger.node.ts
Normal file
|
@ -0,0 +1,329 @@
|
|||
import {
|
||||
IHookFunctions,
|
||||
IWebhookFunctions,
|
||||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
INodeTypeDescription,
|
||||
INodeType,
|
||||
IDataObject,
|
||||
IWebhookResponseData,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
apiRequest,
|
||||
eventExists,
|
||||
} from './GenericFunctions';
|
||||
|
||||
interface IEvent {
|
||||
customer?: IDataObject;
|
||||
email?: IDataObject;
|
||||
push?: IDataObject;
|
||||
slack?: IDataObject;
|
||||
sms?: IDataObject;
|
||||
webhook?: IDataObject;
|
||||
}
|
||||
|
||||
export class CustomerIoTrigger implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'Customer.io Trigger',
|
||||
name: 'customerIoTrigger',
|
||||
group: ['trigger'],
|
||||
icon: 'file:customerio.png',
|
||||
version: 1,
|
||||
description: 'Starts the workflow on a Customer.io update. (Beta)',
|
||||
defaults: {
|
||||
name: 'Customer.io Trigger',
|
||||
color: '#7131ff',
|
||||
},
|
||||
inputs: [],
|
||||
outputs: ['main'],
|
||||
credentials: [
|
||||
{
|
||||
name: 'customerIoApi',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
webhooks: [
|
||||
{
|
||||
name: 'default',
|
||||
httpMethod: 'POST',
|
||||
responseMode: 'onReceived',
|
||||
path: 'webhook',
|
||||
},
|
||||
],
|
||||
properties: [
|
||||
{
|
||||
displayName: 'Events',
|
||||
name: 'events',
|
||||
type: 'multiOptions',
|
||||
required: true,
|
||||
default: [],
|
||||
description: 'The events that can trigger the webhook and whether they are enabled.',
|
||||
options: [
|
||||
{
|
||||
name: 'Customer Subscribed',
|
||||
value: 'customer.subscribed',
|
||||
description: 'Whether the webhook is triggered when a list subscriber is added.',
|
||||
},
|
||||
{
|
||||
name: 'Customer Unsubscribe',
|
||||
value: 'customer.unsubscribed',
|
||||
description: 'Whether the webhook is triggered when a list member unsubscribes.',
|
||||
},
|
||||
{
|
||||
name: 'Email Attempted',
|
||||
value: 'email.attempted',
|
||||
description: 'Whether the webhook is triggered when a list member unsubscribes.',
|
||||
},
|
||||
{
|
||||
name: 'Email Bounced',
|
||||
value: 'email.bounced',
|
||||
description: 'Whether the webhook is triggered when a list member unsubscribes.',
|
||||
},
|
||||
{
|
||||
name: 'Email clicked',
|
||||
value: 'email.clicked',
|
||||
description: 'Whether the webhook is triggered when a list member unsubscribes.',
|
||||
},
|
||||
{
|
||||
name: 'Email converted',
|
||||
value: 'email.converted',
|
||||
description: 'Whether the webhook is triggered when a list member unsubscribes.',
|
||||
},
|
||||
{
|
||||
name: 'Email delivered',
|
||||
value: 'email.delivered',
|
||||
description: 'Whether the webhook is triggered when a list member unsubscribes.',
|
||||
},
|
||||
{
|
||||
name: 'Email drafted',
|
||||
value: 'email.drafted',
|
||||
description: 'Whether the webhook is triggered when a list member unsubscribes.',
|
||||
},
|
||||
{
|
||||
name: 'Email failed',
|
||||
value: 'email.failed',
|
||||
description: 'Whether the webhook is triggered when a list member unsubscribes.',
|
||||
},
|
||||
{
|
||||
name: 'Email opened',
|
||||
value: 'email.opened',
|
||||
description: 'Whether the webhook is triggered when a list member unsubscribes.',
|
||||
},
|
||||
{
|
||||
name: 'Email sent',
|
||||
value: 'email.sent',
|
||||
description: 'Whether the webhook is triggered when a list member unsubscribes.',
|
||||
},
|
||||
{
|
||||
name: 'Email spammed',
|
||||
value: 'email.spammed',
|
||||
description: 'Whether the webhook is triggered when a list member unsubscribes.',
|
||||
},
|
||||
{
|
||||
name: 'Push attempted',
|
||||
value: 'push.attempted',
|
||||
description: 'Whether the webhook is triggered when a list member unsubscribes.',
|
||||
},
|
||||
{
|
||||
name: 'Push bounced',
|
||||
value: 'push.bounced',
|
||||
description: 'Whether the webhook is triggered when a list member unsubscribes.',
|
||||
},
|
||||
{
|
||||
name: 'Push clicked',
|
||||
value: 'push.clicked',
|
||||
description: 'Whether the webhook is triggered when a list member unsubscribes.',
|
||||
},
|
||||
{
|
||||
name: 'Push delivered',
|
||||
value: 'push.delivered',
|
||||
description: 'Whether the webhook is triggered when a list member unsubscribes.',
|
||||
},
|
||||
{
|
||||
name: 'Push drafted',
|
||||
value: 'push.drafted',
|
||||
description: 'Whether the webhook is triggered when a list member unsubscribes.',
|
||||
},
|
||||
{
|
||||
name: 'Push failed',
|
||||
value: 'push.failed',
|
||||
description: 'Whether the webhook is triggered when a list member unsubscribes.',
|
||||
},
|
||||
{
|
||||
name: 'Push opened',
|
||||
value: 'push.opened',
|
||||
description: 'Whether the webhook is triggered when a list member unsubscribes.',
|
||||
},
|
||||
{
|
||||
name: 'Push sent',
|
||||
value: 'push.sent',
|
||||
description: 'Whether the webhook is triggered when a list member unsubscribes.',
|
||||
},
|
||||
{
|
||||
name: 'Slack attempted',
|
||||
value: 'slack.attempted',
|
||||
description: 'Whether the webhook is triggered when a list member unsubscribes.',
|
||||
},
|
||||
{
|
||||
name: 'Slack clicked',
|
||||
value: 'slack.clicked',
|
||||
description: 'Whether the webhook is triggered when a list member unsubscribes.',
|
||||
},
|
||||
{
|
||||
name: 'Slack drafted',
|
||||
value: 'slack.drafted',
|
||||
description: 'Whether the webhook is triggered when a list member unsubscribes.',
|
||||
},
|
||||
{
|
||||
name: 'Slack failed',
|
||||
value: 'slack.failed',
|
||||
description: 'Whether the webhook is triggered when a list member unsubscribes.',
|
||||
},
|
||||
{
|
||||
name: 'Slack sent',
|
||||
value: 'slack.sent',
|
||||
description: 'Whether the webhook is triggered when a list member unsubscribes.',
|
||||
},
|
||||
{
|
||||
name: 'SMS attempted',
|
||||
value: 'sms.attempted',
|
||||
description: 'Whether the webhook is triggered when a list member unsubscribes.',
|
||||
},
|
||||
{
|
||||
name: 'SMS bounced',
|
||||
value: 'sms.bounced',
|
||||
description: 'Whether the webhook is triggered when a list member unsubscribes.',
|
||||
},
|
||||
{
|
||||
name: 'SMS clicked',
|
||||
value: 'sms.clicked',
|
||||
description: 'Whether the webhook is triggered when a list member unsubscribes.',
|
||||
},
|
||||
{
|
||||
name: 'SMS delivered',
|
||||
value: 'sms.delivered',
|
||||
description: 'Whether the webhook is triggered when a list member unsubscribes.',
|
||||
},
|
||||
{
|
||||
name: 'SMS drafted',
|
||||
value: 'sms.drafted',
|
||||
description: 'Whether the webhook is triggered when a list member unsubscribes.',
|
||||
},
|
||||
{
|
||||
name: 'SMS failed',
|
||||
value: 'sms.failed',
|
||||
description: 'Whether the webhook is triggered when a list member unsubscribes.',
|
||||
},
|
||||
{
|
||||
name: 'SMS sent',
|
||||
value: 'sms.sent',
|
||||
description: 'Whether the webhook is triggered when a list member unsubscribes.',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
// @ts-ignore (because of request)
|
||||
webhookMethods = {
|
||||
default: {
|
||||
async checkExists(this: IHookFunctions): Promise<boolean> {
|
||||
const webhookUrl = this.getNodeWebhookUrl('default');
|
||||
|
||||
const webhookData = this.getWorkflowStaticData('node');
|
||||
|
||||
const currentEvents = this.getNodeParameter('events', []) as string[];
|
||||
|
||||
const endpoint = '/reporting_webhooks';
|
||||
|
||||
let { reporting_webhooks: webhooks } = await apiRequest.call(this, 'GET', endpoint, {});
|
||||
|
||||
if (webhooks === null) {
|
||||
webhooks = [];
|
||||
}
|
||||
|
||||
for (const webhook of webhooks) {
|
||||
if (webhook.endpoint === webhookUrl &&
|
||||
eventExists(currentEvents, webhook.events)) {
|
||||
webhookData.webhookId = webhook.id;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
async create(this: IHookFunctions): Promise<boolean> {
|
||||
let webhook;
|
||||
const webhookUrl = this.getNodeWebhookUrl('default');
|
||||
const events = this.getNodeParameter('events', []) as string[];
|
||||
|
||||
const endpoint = '/reporting_webhooks';
|
||||
|
||||
const data: IEvent = {
|
||||
customer: {},
|
||||
email: {},
|
||||
push: {},
|
||||
slack: {},
|
||||
sms: {},
|
||||
webhook: {},
|
||||
};
|
||||
|
||||
for (const event of events) {
|
||||
const option = event.split('.')[1];
|
||||
if (event.startsWith('customer')) {
|
||||
data.customer![option] = true;
|
||||
}
|
||||
if (event.startsWith('email')) {
|
||||
data.email![option] = true;
|
||||
}
|
||||
if (event.startsWith('push')) {
|
||||
data.push![option] = true;
|
||||
}
|
||||
if (event.startsWith('slack')) {
|
||||
data.slack![option] = true;
|
||||
}
|
||||
if (event.startsWith('sms')) {
|
||||
data.sms![option] = true;
|
||||
}
|
||||
if (event.startsWith('webhook')) {
|
||||
data.webhook![option] = true;
|
||||
}
|
||||
}
|
||||
const body = {
|
||||
endpoint: webhookUrl,
|
||||
events: data,
|
||||
};
|
||||
|
||||
webhook = await apiRequest.call(this, 'POST', endpoint, body);
|
||||
|
||||
const webhookData = this.getWorkflowStaticData('node');
|
||||
webhookData.webhookId = webhook.id as string;
|
||||
return true;
|
||||
},
|
||||
async delete(this: IHookFunctions): Promise<boolean> {
|
||||
const webhookData = this.getWorkflowStaticData('node');
|
||||
|
||||
if (webhookData.webhookId !== undefined) {
|
||||
const endpoint = `/reporting_webhooks/${webhookData.webhookId}`;
|
||||
try {
|
||||
await apiRequest.call(this, 'DELETE', endpoint, {});
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
delete webhookData.webhookId;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
async webhook(this: IWebhookFunctions): Promise<IWebhookResponseData> {
|
||||
const bodyData = this.getBodyData();
|
||||
return {
|
||||
workflowData: [
|
||||
this.helpers.returnJsonArray(bodyData)
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
65
packages/nodes-base/nodes/CustomerIo/GenericFunctions.ts
Normal file
65
packages/nodes-base/nodes/CustomerIo/GenericFunctions.ts
Normal file
|
@ -0,0 +1,65 @@
|
|||
import {
|
||||
IExecuteFunctions,
|
||||
IHookFunctions,
|
||||
ILoadOptionsFunctions,
|
||||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
OptionsWithUri,
|
||||
} from 'request';
|
||||
|
||||
import {
|
||||
IDataObject,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
get,
|
||||
} from 'lodash';
|
||||
|
||||
export async function apiRequest(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions, method: string, endpoint: string, body: object, query?: IDataObject): Promise<any> { // tslint:disable-line:no-any
|
||||
const credentials = this.getCredentials('customerIoApi');
|
||||
|
||||
if (credentials === undefined) {
|
||||
throw new Error('No credentials got returned!');
|
||||
}
|
||||
|
||||
query = query || {};
|
||||
|
||||
const options: OptionsWithUri = {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${credentials.apiKey}`,
|
||||
},
|
||||
method,
|
||||
body,
|
||||
qs: query,
|
||||
uri: `https://beta-api.customer.io/v1/api${endpoint}`,
|
||||
json: true,
|
||||
};
|
||||
try {
|
||||
return await this.helpers.request!(options);
|
||||
} catch (error) {
|
||||
if (error.statusCode === 401) {
|
||||
// Return a clear error
|
||||
throw new Error('The Customer.io credentials are not valid!');
|
||||
}
|
||||
|
||||
if (error.response && error.response.body && error.response.body.error_code) {
|
||||
// Try to return the error prettier
|
||||
const errorBody = error.response.body;
|
||||
throw new Error(`Customer.io error response [${errorBody.error_code}]: ${errorBody.description}`);
|
||||
}
|
||||
|
||||
// Expected error data did not get returned so throw the actual error
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export function eventExists(currentEvents: string[], webhookEvents: IDataObject) {
|
||||
for (const currentEvent of currentEvents) {
|
||||
if (get(webhookEvents, `${currentEvent.split('.')[0]}.${currentEvent.split('.')[1]}`) !== true) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
|
@ -133,7 +133,7 @@ export class FacebookGraphApi implements INodeType {
|
|||
name: 'edge',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Edge of the node on which to operate. Edges represent collections of objects wich are attached to the node.',
|
||||
description: 'Edge of the node on which to operate. Edges represent collections of objects which are attached to the node.',
|
||||
placeholder: 'videos',
|
||||
required: false,
|
||||
},
|
||||
|
|
|
@ -99,7 +99,7 @@ export class FlowTrigger implements INodeType {
|
|||
]
|
||||
}
|
||||
},
|
||||
description: `Taks ids separated by ,`,
|
||||
description: `Task ids separated by ,`,
|
||||
},
|
||||
],
|
||||
|
||||
|
|
|
@ -181,7 +181,7 @@ export class GoogleSheet {
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns the given sheet data in a strucutred way
|
||||
* Returns the given sheet data in a structured way
|
||||
*/
|
||||
structureData(inputData: string[][], startRow: number, keys: string[], addEmpty?: boolean): IDataObject[] {
|
||||
const returnData = [];
|
||||
|
@ -208,7 +208,7 @@ export class GoogleSheet {
|
|||
|
||||
|
||||
/**
|
||||
* Returns the given sheet data in a strucutred way using
|
||||
* Returns the given sheet data in a structured way using
|
||||
* the startRow as the one with the name of the key
|
||||
*/
|
||||
structureArrayDataByColumn(inputData: string[][], keyRow: number, dataStartRow: number): IDataObject[] {
|
||||
|
@ -216,7 +216,7 @@ export class GoogleSheet {
|
|||
const keys: string[] = [];
|
||||
|
||||
if (keyRow < 0 || dataStartRow < keyRow || keyRow >= inputData.length) {
|
||||
// The key row does not exist so it is not possible to strucutre data
|
||||
// The key row does not exist so it is not possible to structure data
|
||||
return [];
|
||||
}
|
||||
|
||||
|
|
|
@ -151,7 +151,7 @@ export class GoogleSheets implements INodeType {
|
|||
displayName: 'To Delete',
|
||||
name: 'toDelete',
|
||||
placeholder: 'Add Columns/Rows to delete',
|
||||
description: 'Deletes colums and rows from a sheet.',
|
||||
description: 'Deletes columns and rows from a sheet.',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
|
@ -363,7 +363,7 @@ export class GoogleSheets implements INodeType {
|
|||
},
|
||||
},
|
||||
default: 0,
|
||||
description: 'Index of the row which contains the keys. Starts at 0.<br />The incoming node data is matched to the keys for assignment. The matching is case sensitve.',
|
||||
description: 'Index of the row which contains the keys. Starts at 0.<br />The incoming node data is matched to the keys for assignment. The matching is case sensitive.',
|
||||
},
|
||||
|
||||
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
import {
|
||||
IHookFunctions,
|
||||
IWebhookFunctions,
|
||||
} from 'n8n-core';
|
||||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
import {
|
||||
IDataObject,
|
||||
INodeTypeDescription,
|
||||
INodeType,
|
||||
IWebhookResponseData,
|
||||
ILoadOptionsFunctions,
|
||||
INodePropertyOptions,
|
||||
} from 'n8n-workflow';
|
||||
import {
|
||||
} from 'n8n-workflow';
|
||||
import {
|
||||
mailchimpApiRequest,
|
||||
} from './GenericFunctions';
|
||||
} from './GenericFunctions';
|
||||
|
||||
export class MailchimpTrigger implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
|
@ -24,8 +24,8 @@ export class MailchimpTrigger implements INodeType {
|
|||
version: 1,
|
||||
description: 'Handle Mailchimp events via webhooks',
|
||||
defaults: {
|
||||
name: 'Mailchimp Trigger',
|
||||
color: '#32325d',
|
||||
name: 'Mailchimp Trigger',
|
||||
color: '#32325d',
|
||||
},
|
||||
inputs: [],
|
||||
outputs: ['main'],
|
||||
|
@ -285,8 +285,8 @@ export class MailchimpTrigger implements INodeType {
|
|||
}
|
||||
// @ts-ignore
|
||||
if (!webhookData.events.includes(req.body.type)
|
||||
// @ts-ignore
|
||||
&& !webhookData.sources.includes(req.body.type)) {
|
||||
// @ts-ignore
|
||||
&& !webhookData.sources.includes(req.body.type)) {
|
||||
return {};
|
||||
}
|
||||
return {
|
||||
|
|
54
packages/nodes-base/nodes/Medium/GenericFunctions.ts
Normal file
54
packages/nodes-base/nodes/Medium/GenericFunctions.ts
Normal file
|
@ -0,0 +1,54 @@
|
|||
import {
|
||||
OptionsWithUri,
|
||||
} from 'request';
|
||||
|
||||
import {
|
||||
IExecuteFunctions,
|
||||
IExecuteSingleFunctions,
|
||||
IHookFunctions,
|
||||
ILoadOptionsFunctions,
|
||||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
IDataObject,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export async function mediumApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, endpoint: string, body: any = {}, query: IDataObject = {}, uri?: string): Promise<any> { // tslint:disable-line:no-any
|
||||
|
||||
const authenticationMethod = this.getNodeParameter('authentication', 0);
|
||||
|
||||
const options: OptionsWithUri = {
|
||||
method,
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
'Accept-Charset': 'utf-8',
|
||||
},
|
||||
qs: query,
|
||||
uri: uri || `https://api.medium.com/v1${endpoint}`,
|
||||
body,
|
||||
json: true,
|
||||
};
|
||||
|
||||
try {
|
||||
if (authenticationMethod === 'accessToken') {
|
||||
const credentials = this.getCredentials('mediumApi');
|
||||
|
||||
if (credentials === undefined) {
|
||||
throw new Error('No credentials got returned!');
|
||||
}
|
||||
|
||||
options.headers!['Authorization'] = `Bearer ${credentials.accessToken}`;
|
||||
|
||||
return await this.helpers.request!(options);
|
||||
}
|
||||
else {
|
||||
return await this.helpers.requestOAuth2!.call(this, 'mediumOAuth2Api', options);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.statusCode === 401) {
|
||||
throw new Error('The Medium credentials are not valid!');
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
577
packages/nodes-base/nodes/Medium/Medium.node.ts
Normal file
577
packages/nodes-base/nodes/Medium/Medium.node.ts
Normal file
|
@ -0,0 +1,577 @@
|
|||
import {
|
||||
IExecuteFunctions,
|
||||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
IDataObject,
|
||||
ILoadOptionsFunctions,
|
||||
INodeExecutionData,
|
||||
INodeType,
|
||||
INodePropertyOptions,
|
||||
INodeTypeDescription,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
mediumApiRequest,
|
||||
} from './GenericFunctions';
|
||||
|
||||
export class Medium implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'Medium',
|
||||
name: 'medium',
|
||||
group: ['output'],
|
||||
icon: 'file:medium.png',
|
||||
version: 1,
|
||||
description: 'Consume Medium API',
|
||||
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
|
||||
defaults: {
|
||||
name: 'Medium',
|
||||
color: '#000000',
|
||||
},
|
||||
inputs: ['main'],
|
||||
outputs: ['main'],
|
||||
credentials: [
|
||||
{
|
||||
name: 'mediumApi',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
authentication: [
|
||||
'accessToken',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'mediumOAuth2Api',
|
||||
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 method of authentication.',
|
||||
},
|
||||
{
|
||||
displayName: 'Resource',
|
||||
name: 'resource',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Post',
|
||||
value: 'post',
|
||||
},
|
||||
{
|
||||
name: 'Publication',
|
||||
value: 'publication',
|
||||
},
|
||||
],
|
||||
default: 'post',
|
||||
description: 'Resource to operate on.',
|
||||
},
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'post',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Create',
|
||||
value: 'create',
|
||||
description: 'Create a post',
|
||||
},
|
||||
],
|
||||
default: 'create',
|
||||
description: 'The operation to perform.',
|
||||
},
|
||||
|
||||
// ----------------------------------
|
||||
// post:create
|
||||
// ----------------------------------
|
||||
{
|
||||
displayName: 'Publication',
|
||||
name: 'publication',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'post',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: false,
|
||||
description: 'Are you posting for a publication?'
|
||||
},
|
||||
{
|
||||
displayName: 'Publication ID',
|
||||
name: 'publicationId',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'post',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
publication: [
|
||||
true,
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getPublications',
|
||||
},
|
||||
default: '',
|
||||
description: 'Publication ids',
|
||||
},
|
||||
{
|
||||
displayName: 'Title',
|
||||
name: 'title',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'My Open Source Contribution',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
resource: [
|
||||
'post',
|
||||
],
|
||||
},
|
||||
},
|
||||
description: 'Title of the post. Max Length : 100 characters',
|
||||
},
|
||||
{
|
||||
displayName: 'Content Format',
|
||||
name: 'contentFormat',
|
||||
default: '',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
resource: [
|
||||
'post',
|
||||
],
|
||||
},
|
||||
},
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'HTML',
|
||||
value: 'html',
|
||||
},
|
||||
{
|
||||
name: 'Markdown',
|
||||
value: 'markdown',
|
||||
},
|
||||
],
|
||||
description: 'The format of the content to be posted.',
|
||||
},
|
||||
{
|
||||
displayName: 'Content',
|
||||
name: 'content',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'My open source contribution',
|
||||
required: true,
|
||||
typeOptions: {
|
||||
alwaysOpenEditWindow: true,
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
resource: [
|
||||
'post',
|
||||
],
|
||||
},
|
||||
},
|
||||
description: 'The body of the post, in a valid semantic HTML fragment, or Markdown.',
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
resource: [
|
||||
'post',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Canonical Url',
|
||||
name: 'canonicalUrl',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'The original home of this content, if it was originally published elsewhere.',
|
||||
},
|
||||
{
|
||||
displayName: 'License',
|
||||
name: 'license',
|
||||
type: 'options',
|
||||
default: 'all-rights-reserved',
|
||||
options: [
|
||||
{
|
||||
name: 'all-rights-reserved',
|
||||
value: 'all-rights-reserved',
|
||||
},
|
||||
{
|
||||
name: 'cc-40-by',
|
||||
value: 'cc-40-by',
|
||||
},
|
||||
{
|
||||
name: 'cc-40-by-nc',
|
||||
value: 'cc-40-by-nc',
|
||||
},
|
||||
{
|
||||
name: 'cc-40-by-nc-nd',
|
||||
value: 'cc-40-by-nc-nd',
|
||||
},
|
||||
{
|
||||
name: 'cc-40-by-nc-sa',
|
||||
value: 'cc-40-by-nc-sa',
|
||||
},
|
||||
{
|
||||
name: 'cc-40-by-nd',
|
||||
value: 'cc-40-by-nd',
|
||||
},
|
||||
{
|
||||
name: 'cc-40-by-sa',
|
||||
value: 'cc-40-by-sa',
|
||||
},
|
||||
{
|
||||
name: 'cc-40-zero',
|
||||
value: 'cc-40-zero',
|
||||
},
|
||||
{
|
||||
name: 'public-domain',
|
||||
value: 'public-domain',
|
||||
},
|
||||
],
|
||||
description: 'License of the post.',
|
||||
},
|
||||
{
|
||||
displayName: 'Notify Followers',
|
||||
name: 'notifyFollowers',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Whether to notify followers that the user has published.',
|
||||
},
|
||||
{
|
||||
displayName: 'Publish Status',
|
||||
name: 'publishStatus',
|
||||
default: 'public',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Public',
|
||||
value: 'public',
|
||||
},
|
||||
{
|
||||
name: 'Draft',
|
||||
value: 'draft',
|
||||
},
|
||||
{
|
||||
name: 'Unlisted',
|
||||
value: 'unlisted',
|
||||
},
|
||||
],
|
||||
description: 'The status of the post.',
|
||||
},
|
||||
{
|
||||
displayName: 'Tags',
|
||||
name: 'tags',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'open-source,mlh,fellowship',
|
||||
description: 'Comma-separated strings to be used as tags for post classification. Max allowed tags: 5. Max tag length: 25 characters.',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'publication',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Get All',
|
||||
value: 'getAll',
|
||||
description: 'Get all publications',
|
||||
},
|
||||
],
|
||||
default: 'publication',
|
||||
description: 'The operation to perform.',
|
||||
},
|
||||
// ----------------------------------
|
||||
// publication:getAll
|
||||
// ----------------------------------
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
resource: [
|
||||
'publication',
|
||||
],
|
||||
},
|
||||
},
|
||||
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: [
|
||||
'publication',
|
||||
],
|
||||
returnAll: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 200,
|
||||
},
|
||||
default: 100,
|
||||
description: 'How many results to return.',
|
||||
},
|
||||
],
|
||||
};
|
||||
methods = {
|
||||
loadOptions: {
|
||||
// Get all the available publications to display them to user so that he can
|
||||
// select them easily
|
||||
async getPublications(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||
const returnData: INodePropertyOptions[] = [];
|
||||
//Get the User Id
|
||||
const user = await mediumApiRequest.call(
|
||||
this,
|
||||
'GET',
|
||||
`/me`,
|
||||
);
|
||||
|
||||
const userId = user.data.id;
|
||||
//Get all publications of that user
|
||||
const publications = await mediumApiRequest.call(
|
||||
this,
|
||||
'GET',
|
||||
`/users/${userId}/publications`,
|
||||
);
|
||||
const publicationsList = publications.data;
|
||||
for (const publication of publicationsList) {
|
||||
const publicationName = publication.name;
|
||||
const publicationId = publication.id;
|
||||
returnData.push({
|
||||
name: publicationName,
|
||||
value: publicationId,
|
||||
});
|
||||
}
|
||||
return returnData;
|
||||
},
|
||||
|
||||
},
|
||||
};
|
||||
|
||||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
|
||||
const items = this.getInputData();
|
||||
const returnData: IDataObject[] = [];
|
||||
|
||||
let operation: string;
|
||||
let resource: string;
|
||||
|
||||
// For POST
|
||||
let bodyRequest: IDataObject;
|
||||
// For Query string
|
||||
let qs: IDataObject;
|
||||
let responseData;
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
qs = {};
|
||||
|
||||
resource = this.getNodeParameter('resource', i) as string;
|
||||
operation = this.getNodeParameter('operation', i) as string;
|
||||
|
||||
if (resource === 'post') {
|
||||
//https://github.com/Medium/medium-api-docs
|
||||
if (operation === 'create') {
|
||||
// ----------------------------------
|
||||
// post:create
|
||||
// ----------------------------------
|
||||
|
||||
const title = this.getNodeParameter('title', i) as string;
|
||||
const contentFormat = this.getNodeParameter('contentFormat', i) as string;
|
||||
const content = this.getNodeParameter('content', i) as string;
|
||||
bodyRequest = {
|
||||
tags: [],
|
||||
title,
|
||||
contentFormat,
|
||||
content,
|
||||
|
||||
};
|
||||
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||
if (additionalFields.tags) {
|
||||
const tags = additionalFields.tags as string;
|
||||
bodyRequest.tags = tags.split(',').map(name => {
|
||||
const returnValue = name.trim();
|
||||
if (returnValue.length > 25) {
|
||||
throw new Error(`The tag "${returnValue}" is to long. Maximum lenght of a tag is 25 characters.`);
|
||||
}
|
||||
return returnValue;
|
||||
});
|
||||
|
||||
if ((bodyRequest.tags as string[]).length > 5) {
|
||||
throw new Error('To many tags got used. Maximum 5 can be set.');
|
||||
}
|
||||
}
|
||||
|
||||
if (additionalFields.canonicalUrl) {
|
||||
bodyRequest.canonicalUrl = additionalFields.canonicalUrl as string;
|
||||
}
|
||||
if (additionalFields.publishStatus) {
|
||||
bodyRequest.publishStatus = additionalFields.publishStatus as string;
|
||||
}
|
||||
if (additionalFields.license) {
|
||||
bodyRequest.license = additionalFields.license as string;
|
||||
}
|
||||
if (additionalFields.notifyFollowers) {
|
||||
bodyRequest.notifyFollowers = additionalFields.notifyFollowers as string;
|
||||
}
|
||||
|
||||
const underPublication = this.getNodeParameter('publication', i) as boolean;
|
||||
|
||||
// if user wants to publish it under a specific publication
|
||||
if (underPublication) {
|
||||
const publicationId = this.getNodeParameter('publicationId', i) as number;
|
||||
|
||||
responseData = await mediumApiRequest.call(
|
||||
this,
|
||||
'POST',
|
||||
`/publications/${publicationId}/posts`,
|
||||
bodyRequest,
|
||||
qs
|
||||
);
|
||||
}
|
||||
else {
|
||||
const responseAuthorId = await mediumApiRequest.call(
|
||||
this,
|
||||
'GET',
|
||||
'/me',
|
||||
{},
|
||||
qs
|
||||
);
|
||||
|
||||
const authorId = responseAuthorId.data.id;
|
||||
responseData = await mediumApiRequest.call(
|
||||
this,
|
||||
'POST',
|
||||
`/users/${authorId}/posts`,
|
||||
bodyRequest,
|
||||
qs
|
||||
);
|
||||
|
||||
responseData = responseData.data;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (resource === 'publication') {
|
||||
//https://github.com/Medium/medium-api-docs#32-publications
|
||||
if (operation === 'getAll') {
|
||||
// ----------------------------------
|
||||
// publication:getAll
|
||||
// ----------------------------------
|
||||
|
||||
const returnAll = this.getNodeParameter('returnAll', i) as string;
|
||||
|
||||
const user = await mediumApiRequest.call(
|
||||
this,
|
||||
'GET',
|
||||
`/me`,
|
||||
);
|
||||
|
||||
const userId = user.data.id;
|
||||
//Get all publications of that user
|
||||
responseData = await mediumApiRequest.call(
|
||||
this,
|
||||
'GET',
|
||||
`/users/${userId}/publications`,
|
||||
);
|
||||
|
||||
responseData = responseData.data;
|
||||
|
||||
if (!returnAll) {
|
||||
const limit = this.getNodeParameter('limit', i) as number;
|
||||
responseData = responseData.splice(0, limit);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Array.isArray(responseData)) {
|
||||
returnData.push.apply(returnData, responseData as IDataObject[]);
|
||||
} else {
|
||||
returnData.push(responseData as IDataObject);
|
||||
}
|
||||
}
|
||||
return [this.helpers.returnJsonArray(returnData)];
|
||||
}
|
||||
}
|
BIN
packages/nodes-base/nodes/Medium/medium.png
Normal file
BIN
packages/nodes-base/nodes/Medium/medium.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.2 KiB |
|
@ -18,12 +18,12 @@ export const lightOperations = [
|
|||
{
|
||||
name: 'Delete',
|
||||
value: 'delete',
|
||||
description: 'Delete an light',
|
||||
description: 'Delete a light',
|
||||
},
|
||||
{
|
||||
name: 'Get',
|
||||
value: 'get',
|
||||
description: 'Retrieve an light',
|
||||
description: 'Retrieve a light',
|
||||
},
|
||||
{
|
||||
name: 'Get All',
|
||||
|
@ -194,9 +194,9 @@ export const lightFields = [
|
|||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'none',
|
||||
name: 'None',
|
||||
value: 'none',
|
||||
description: 'the light is not performing an alert effect',
|
||||
description: 'The light is not performing an alert effect',
|
||||
},
|
||||
{
|
||||
name: 'Select',
|
||||
|
@ -275,7 +275,7 @@ export const lightFields = [
|
|||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'none',
|
||||
name: 'None',
|
||||
value: 'none',
|
||||
},
|
||||
{
|
||||
|
|
|
@ -36,7 +36,7 @@ export const opportunityOperations = [
|
|||
{
|
||||
name: 'Get All',
|
||||
value: 'getAll',
|
||||
description: 'Get all opportunitys',
|
||||
description: 'Get all opportunities',
|
||||
},
|
||||
{
|
||||
name: 'Get Summary',
|
||||
|
|
|
@ -119,7 +119,7 @@ export const taskFields = [
|
|||
type: 'number',
|
||||
default: '',
|
||||
description: `Duration of the call in seconds. Not subject to field-level security,<br/>
|
||||
available for any user in an organization with Salesforce CRM Call Cente`,
|
||||
available for any user in an organization with Salesforce CRM Call Center`,
|
||||
},
|
||||
{
|
||||
displayName: 'Call Object',
|
||||
|
@ -434,7 +434,7 @@ export const taskFields = [
|
|||
type: 'number',
|
||||
default: '',
|
||||
description: `Duration of the call in seconds. Not subject to field-level security,<br/>
|
||||
available for any user in an organization with Salesforce CRM Call Cente`,
|
||||
available for any user in an organization with Salesforce CRM Call Center`,
|
||||
},
|
||||
{
|
||||
displayName: 'Call Object',
|
||||
|
|
356
packages/nodes-base/nodes/TravisCi/BuildDescription.ts
Normal file
356
packages/nodes-base/nodes/TravisCi/BuildDescription.ts
Normal file
|
@ -0,0 +1,356 @@
|
|||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const buildOperations = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'build',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Cancel',
|
||||
value: 'cancel',
|
||||
description: 'Cancel a build',
|
||||
},
|
||||
{
|
||||
name: 'Get',
|
||||
value: 'get',
|
||||
description: 'Get a build',
|
||||
},
|
||||
{
|
||||
name: 'Get All',
|
||||
value: 'getAll',
|
||||
description: 'Get all builds',
|
||||
},
|
||||
{
|
||||
name: 'Restart',
|
||||
value: 'restart',
|
||||
description: 'Restart a build',
|
||||
},
|
||||
{
|
||||
name: 'Trigger',
|
||||
value: 'trigger',
|
||||
description: 'Trigger a build',
|
||||
},
|
||||
],
|
||||
default: 'cancel',
|
||||
description: 'The operation to perform.',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
export const buildFields = [
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* build:cancel */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Build ID',
|
||||
name: 'buildId',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'cancel',
|
||||
],
|
||||
resource: [
|
||||
'build',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'Value uniquely identifying the build.',
|
||||
},
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* build:get */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Build ID',
|
||||
name: 'buildId',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'get',
|
||||
],
|
||||
resource: [
|
||||
'build',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'Value uniquely identifying the build.',
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'build',
|
||||
],
|
||||
operation: [
|
||||
'get',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Include',
|
||||
name: 'include',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'build.commit',
|
||||
description: 'List of attributes to eager load.',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* build:getAll */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
resource: [
|
||||
'build',
|
||||
],
|
||||
},
|
||||
},
|
||||
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: [
|
||||
'build',
|
||||
],
|
||||
returnAll: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 500,
|
||||
},
|
||||
default: 100,
|
||||
description: 'How many results to return.',
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'build',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Include',
|
||||
name: 'include',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'build.commit',
|
||||
description: 'List of attributes to eager load.',
|
||||
},
|
||||
{
|
||||
displayName: 'Order',
|
||||
name: 'order',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'ASC',
|
||||
value: 'asc',
|
||||
},
|
||||
{
|
||||
name: 'DESC',
|
||||
value: 'desc',
|
||||
}
|
||||
],
|
||||
default: 'asc',
|
||||
description: 'You may specify order to sort your response.',
|
||||
},
|
||||
{
|
||||
displayName: 'Sort By',
|
||||
name: 'sortBy',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'ID',
|
||||
value: 'id',
|
||||
},
|
||||
{
|
||||
name: 'Created At',
|
||||
value: 'created_at',
|
||||
},
|
||||
{
|
||||
name: 'Started At',
|
||||
value: 'started_at',
|
||||
},
|
||||
{
|
||||
name: 'Finished At',
|
||||
value: 'finished_at',
|
||||
},
|
||||
{
|
||||
name: 'Finished At',
|
||||
value: 'finished_at',
|
||||
},
|
||||
{
|
||||
name: 'Number',
|
||||
value: 'number',
|
||||
},
|
||||
],
|
||||
default: 'number',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* build:restart */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Build ID',
|
||||
name: 'buildId',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'restart',
|
||||
],
|
||||
resource: [
|
||||
'build',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'Value uniquely identifying the build.',
|
||||
},
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* build:trigger */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Slug',
|
||||
name: 'slug',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'trigger',
|
||||
],
|
||||
resource: [
|
||||
'build',
|
||||
],
|
||||
},
|
||||
},
|
||||
placeholder: 'n8n-io/n8n',
|
||||
default: '',
|
||||
description: 'Same as {ownerName}/{repositoryName}',
|
||||
},
|
||||
{
|
||||
displayName: 'Branch',
|
||||
name: 'branch',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'trigger',
|
||||
],
|
||||
resource: [
|
||||
'build',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
placeholder: 'master',
|
||||
description: 'Branch requested to be built.',
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'build',
|
||||
],
|
||||
operation: [
|
||||
'trigger',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Message',
|
||||
name: 'message',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Travis-ci status message attached to the request.',
|
||||
},
|
||||
{
|
||||
displayName: 'Merge Mode',
|
||||
name: 'mergeMode',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Deep Merge Append',
|
||||
value: 'deep_merge_append',
|
||||
},
|
||||
{
|
||||
name: 'Deep Merge Prepend',
|
||||
value: 'deep_merge_prepend',
|
||||
},
|
||||
{
|
||||
name: 'Deep Merge',
|
||||
value: 'deep_merge',
|
||||
},
|
||||
{
|
||||
name: 'Merge',
|
||||
value: 'merge',
|
||||
},
|
||||
{
|
||||
name: 'Replace',
|
||||
value: 'replace',
|
||||
},
|
||||
],
|
||||
default: '',
|
||||
description: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
] as INodeProperties[];
|
78
packages/nodes-base/nodes/TravisCi/GenericFunctions.ts
Normal file
78
packages/nodes-base/nodes/TravisCi/GenericFunctions.ts
Normal file
|
@ -0,0 +1,78 @@
|
|||
import {
|
||||
OptionsWithUri,
|
||||
} from 'request';
|
||||
|
||||
import {
|
||||
IExecuteFunctions,
|
||||
IExecuteSingleFunctions,
|
||||
IHookFunctions,
|
||||
ILoadOptionsFunctions,
|
||||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
IDataObject,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
get,
|
||||
} from 'lodash';
|
||||
|
||||
import * as querystring from 'querystring';
|
||||
|
||||
export async function travisciApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
|
||||
const credentials = this.getCredentials('travisCiApi');
|
||||
if (credentials === undefined) {
|
||||
throw new Error('No credentials got returned!');
|
||||
}
|
||||
let options: OptionsWithUri = {
|
||||
headers: {
|
||||
'Travis-API-Version': '3',
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application.json',
|
||||
'Authorization': `token ${credentials.apiToken}`,
|
||||
},
|
||||
method,
|
||||
qs,
|
||||
body,
|
||||
uri: uri || `https://api.travis-ci.com${resource}`,
|
||||
json: true
|
||||
};
|
||||
options = Object.assign({}, options, option);
|
||||
if (Object.keys(options.body).length === 0) {
|
||||
delete options.body;
|
||||
}
|
||||
try {
|
||||
return await this.helpers.request!(options);
|
||||
} catch (err) {
|
||||
if (err.response && err.response.body && err.response.body.error_message) {
|
||||
// Try to return the error prettier
|
||||
throw new Error(`TravisCI error response [${err.statusCode}]: ${err.response.body.error_message}`);
|
||||
}
|
||||
|
||||
// If that data does not exist for some reason return the actual error
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make an API request to paginated TravisCI endpoint
|
||||
* and return all results
|
||||
*/
|
||||
export async function travisciApiRequestAllItems(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions, propertyName: string, method: string, resource: string, body: any = {}, query: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
|
||||
|
||||
const returnData: IDataObject[] = [];
|
||||
|
||||
let responseData;
|
||||
|
||||
do {
|
||||
responseData = await travisciApiRequest.call(this, method, resource, body, query);
|
||||
const path = get(responseData, '@pagination.next.@href');
|
||||
if (path !== undefined) {
|
||||
query = querystring.parse(path);
|
||||
}
|
||||
returnData.push.apply(returnData, responseData[propertyName]);
|
||||
} while (
|
||||
responseData['@pagination']['is_last'] !== true
|
||||
);
|
||||
return returnData;
|
||||
}
|
151
packages/nodes-base/nodes/TravisCi/TravisCi.node.ts
Normal file
151
packages/nodes-base/nodes/TravisCi/TravisCi.node.ts
Normal file
|
@ -0,0 +1,151 @@
|
|||
import {
|
||||
IExecuteFunctions,
|
||||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
IDataObject,
|
||||
INodeTypeDescription,
|
||||
INodeExecutionData,
|
||||
INodeType,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
buildFields,
|
||||
buildOperations,
|
||||
} from './BuildDescription';
|
||||
|
||||
import {
|
||||
travisciApiRequest,
|
||||
travisciApiRequestAllItems,
|
||||
} from './GenericFunctions';
|
||||
|
||||
export class TravisCi implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'TravisCI',
|
||||
name: 'travisCi',
|
||||
icon: 'file:travisci.png',
|
||||
group: ['output'],
|
||||
version: 1,
|
||||
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
|
||||
description: 'Consume TravisCI API',
|
||||
defaults: {
|
||||
name: 'TravisCI',
|
||||
color: '#666666',
|
||||
},
|
||||
inputs: ['main'],
|
||||
outputs: ['main'],
|
||||
credentials: [
|
||||
{
|
||||
name: 'travisCiApi',
|
||||
required: true,
|
||||
}
|
||||
],
|
||||
properties: [
|
||||
{
|
||||
displayName: 'Resource',
|
||||
name: 'resource',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: ' Build',
|
||||
value: 'build',
|
||||
},
|
||||
],
|
||||
default: 'build',
|
||||
description: 'Resource to consume.',
|
||||
},
|
||||
...buildOperations,
|
||||
...buildFields,
|
||||
],
|
||||
};
|
||||
|
||||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
const items = this.getInputData();
|
||||
const returnData: IDataObject[] = [];
|
||||
const length = items.length as unknown as number;
|
||||
const qs: IDataObject = {};
|
||||
let responseData;
|
||||
const resource = this.getNodeParameter('resource', 0) as string;
|
||||
const operation = this.getNodeParameter('operation', 0) as string;
|
||||
|
||||
for (let i = 0; i < length; i++) {
|
||||
if (resource === 'build') {
|
||||
//https://developer.travis-ci.com/resource/build#find
|
||||
if (operation === 'get') {
|
||||
const buildId = this.getNodeParameter('buildId', i) as string;
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||
|
||||
if (additionalFields.include) {
|
||||
qs.include = additionalFields.include as string;
|
||||
}
|
||||
|
||||
responseData = await travisciApiRequest.call(this, 'GET', `/build/${buildId}`, {}, qs);
|
||||
}
|
||||
//https://developer.travis-ci.com/resource/builds#for_current_user
|
||||
if (operation === 'getAll') {
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
|
||||
|
||||
if (additionalFields.sortBy) {
|
||||
qs.sort_by = additionalFields.sortBy;
|
||||
}
|
||||
|
||||
if (additionalFields.sortBy && additionalFields.order) {
|
||||
qs.sort_by = `${additionalFields.sortBy}:${additionalFields.order}`;
|
||||
}
|
||||
|
||||
if (additionalFields.include) {
|
||||
qs.include = additionalFields.include;
|
||||
}
|
||||
|
||||
if (returnAll === true) {
|
||||
responseData = await travisciApiRequestAllItems.call(this, 'builds', 'GET', '/builds', {}, qs);
|
||||
|
||||
} else {
|
||||
qs.limit = this.getNodeParameter('limit', i) as number;
|
||||
responseData = await travisciApiRequest.call(this, 'GET', '/builds', {}, qs);
|
||||
responseData = responseData.builds;
|
||||
}
|
||||
}
|
||||
//https://developer.travis-ci.com/resource/build#cancel
|
||||
if (operation === 'cancel') {
|
||||
const buildId = this.getNodeParameter('buildId', i) as string;
|
||||
responseData = await travisciApiRequest.call(this, 'POST', `/build/${buildId}/cancel`, {}, qs);
|
||||
}
|
||||
//https://developer.travis-ci.com/resource/build#restart
|
||||
if (operation === 'restart') {
|
||||
const buildId = this.getNodeParameter('buildId', i) as string;
|
||||
responseData = await travisciApiRequest.call(this, 'POST', `/build/${buildId}/restart`, {}, qs);
|
||||
}
|
||||
//https://developer.travis-ci.com/resource/requests#create
|
||||
if (operation === 'trigger') {
|
||||
let slug = this.getNodeParameter('slug', i) as string;
|
||||
const branch = this.getNodeParameter('branch', i) as string;
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||
|
||||
slug = slug.replace(new RegExp(/\//g), '%2F');
|
||||
|
||||
const request: IDataObject = {
|
||||
branch,
|
||||
};
|
||||
|
||||
if (additionalFields.message) {
|
||||
request.message = additionalFields.message as string;
|
||||
}
|
||||
|
||||
if (additionalFields.mergeMode) {
|
||||
request.merge_mode = additionalFields.mergeMode as string;
|
||||
}
|
||||
|
||||
responseData = await travisciApiRequest.call(this, 'POST', `/repo/${slug}/requests`, JSON.stringify({ request }));
|
||||
}
|
||||
}
|
||||
if (Array.isArray(responseData)) {
|
||||
returnData.push.apply(returnData, responseData as IDataObject[]);
|
||||
} else {
|
||||
returnData.push(responseData as IDataObject);
|
||||
}
|
||||
}
|
||||
return [this.helpers.returnJsonArray(returnData)];
|
||||
}
|
||||
}
|
BIN
packages/nodes-base/nodes/TravisCi/travisci.png
Normal file
BIN
packages/nodes-base/nodes/TravisCi/travisci.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.9 KiB |
68
packages/nodes-base/nodes/Twake/GenericFunctions.ts
Normal file
68
packages/nodes-base/nodes/Twake/GenericFunctions.ts
Normal file
|
@ -0,0 +1,68 @@
|
|||
import {
|
||||
IExecuteFunctions,
|
||||
IHookFunctions,
|
||||
ILoadOptionsFunctions,
|
||||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
OptionsWithUri,
|
||||
} from 'request';
|
||||
|
||||
|
||||
/**
|
||||
* Make an API request to Twake
|
||||
*
|
||||
* @param {IHookFunctions} this
|
||||
* @param {string} method
|
||||
* @param {string} url
|
||||
* @param {object} body
|
||||
* @returns {Promise<any>}
|
||||
*/
|
||||
export async function twakeApiRequest(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions, method: string, resource: string, body: object, query?: object, uri?: string): Promise<any> { // tslint:disable-line:no-any
|
||||
|
||||
const authenticationMethod = this.getNodeParameter('twakeVersion', 0, 'twakeCloudApi') as string;
|
||||
|
||||
const options: OptionsWithUri = {
|
||||
headers: {},
|
||||
method,
|
||||
body,
|
||||
qs: query,
|
||||
uri: uri || `https://connectors.albatros.twakeapp.com/n8n${resource}`,
|
||||
json: true,
|
||||
};
|
||||
|
||||
|
||||
// if (authenticationMethod === 'cloud') {
|
||||
const credentials = this.getCredentials('twakeCloudApi');
|
||||
options.headers!.Authorization = `Bearer ${credentials!.workspaceKey}`;
|
||||
|
||||
// } else {
|
||||
// const credentials = this.getCredentials('twakeServerApi');
|
||||
// options.auth = { user: credentials!.publicId as string, pass: credentials!.privateApiKey as string };
|
||||
// options.uri = `${credentials!.hostUrl}/api/v1${resource}`;
|
||||
// }
|
||||
|
||||
try {
|
||||
return await this.helpers.request!(options);
|
||||
} catch (error) {
|
||||
if (error.error.code === 'ECONNREFUSED') {
|
||||
throw new Error('Twake host is not accessible!');
|
||||
|
||||
}
|
||||
if (error.statusCode === 401) {
|
||||
// Return a clear error
|
||||
throw new Error('The Twake credentials are not valid!');
|
||||
}
|
||||
|
||||
if (error.response && error.response.body && error.response.body.errors) {
|
||||
// Try to return the error prettier
|
||||
const errorMessages = error.response.body.errors.map((errorData: { message: string }) => {
|
||||
return errorData.message;
|
||||
});
|
||||
throw new Error(`Twake error response [${error.statusCode}]: ${errorMessages.join(' | ')}`);
|
||||
}
|
||||
|
||||
// If that data does not exist for some reason return the actual error
|
||||
throw error;
|
||||
}
|
||||
}
|
247
packages/nodes-base/nodes/Twake/Twake.node.ts
Normal file
247
packages/nodes-base/nodes/Twake/Twake.node.ts
Normal file
|
@ -0,0 +1,247 @@
|
|||
import {
|
||||
IExecuteFunctions,
|
||||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
INodeExecutionData,
|
||||
IDataObject,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
ILoadOptionsFunctions,
|
||||
INodePropertyOptions,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
twakeApiRequest,
|
||||
} from './GenericFunctions';
|
||||
|
||||
export class Twake implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'Twake',
|
||||
name: 'twake',
|
||||
group: ['transform'],
|
||||
version: 1,
|
||||
icon: 'file:twake.png',
|
||||
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
|
||||
description: 'Consume Twake API',
|
||||
defaults: {
|
||||
name: 'Twake',
|
||||
color: '#7168ee',
|
||||
},
|
||||
inputs: ['main'],
|
||||
outputs: ['main'],
|
||||
credentials: [
|
||||
{
|
||||
name: 'twakeCloudApi',
|
||||
required: true,
|
||||
// displayOptions: {
|
||||
// show: {
|
||||
// twakeVersion: [
|
||||
// 'cloud',
|
||||
// ],
|
||||
// },
|
||||
// },
|
||||
},
|
||||
// {
|
||||
// name: 'twakeServerApi',
|
||||
// required: true,
|
||||
// displayOptions: {
|
||||
// show: {
|
||||
// twakeVersion: [
|
||||
// 'server',
|
||||
// ],
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
],
|
||||
properties: [
|
||||
// {
|
||||
// displayName: 'Twake Version',
|
||||
// name: 'twakeVersion',
|
||||
// type: 'options',
|
||||
// options: [
|
||||
// {
|
||||
// name: 'Cloud',
|
||||
// value: 'cloud',
|
||||
// },
|
||||
// {
|
||||
// name: 'Server (Self Hosted)',
|
||||
// value: 'server',
|
||||
// },
|
||||
// ],
|
||||
// default: 'cloud',
|
||||
// },
|
||||
{
|
||||
displayName: 'Resource',
|
||||
name: 'resource',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Message',
|
||||
value: 'message',
|
||||
description: 'Send data to the message app',
|
||||
},
|
||||
],
|
||||
default: 'message',
|
||||
description: 'The operation to perform.',
|
||||
},
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'message',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Send',
|
||||
value: 'send',
|
||||
description: 'Send a message',
|
||||
},
|
||||
],
|
||||
default: 'send',
|
||||
description: 'The operation to perform.',
|
||||
},
|
||||
{
|
||||
displayName: 'Channel ID',
|
||||
name: 'channelId',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getChannels',
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'send',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: `Channel's ID`,
|
||||
},
|
||||
{
|
||||
displayName: 'Content',
|
||||
name: 'content',
|
||||
type: 'string',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'send',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'Message content',
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'send',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Sender Icon',
|
||||
name: 'senderIcon',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'URL of the image/icon',
|
||||
},
|
||||
{
|
||||
displayName: 'Sender Name',
|
||||
name: 'senderName',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Sender name',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
methods = {
|
||||
loadOptions: {
|
||||
async getChannels(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||
const responseData = await twakeApiRequest.call(this, 'POST', '/channel', {});
|
||||
if (responseData === undefined) {
|
||||
throw new Error('No data got returned');
|
||||
}
|
||||
|
||||
const returnData: INodePropertyOptions[] = [];
|
||||
for (const channel of responseData) {
|
||||
returnData.push({
|
||||
name: channel.name,
|
||||
value: channel.id,
|
||||
});
|
||||
}
|
||||
return returnData;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
const items = this.getInputData();
|
||||
const returnData: IDataObject[] = [];
|
||||
const length = (items.length as unknown) as number;
|
||||
const qs: IDataObject = {};
|
||||
let responseData;
|
||||
const resource = this.getNodeParameter('resource', 0) as string;
|
||||
const operation = this.getNodeParameter('operation', 0) as string;
|
||||
for (let i = 0; i < length; i++) {
|
||||
if (resource === 'message') {
|
||||
if (operation === 'send') {
|
||||
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||
|
||||
const message: IDataObject = {
|
||||
channel_id: this.getNodeParameter('channelId', i),
|
||||
content: {
|
||||
formatted: this.getNodeParameter('content', i) as string,
|
||||
},
|
||||
hidden_data: {
|
||||
allow_delete: 'everyone',
|
||||
},
|
||||
};
|
||||
|
||||
if (additionalFields.senderName) {
|
||||
//@ts-ignore
|
||||
message.hidden_data!.custom_title = additionalFields.senderName as string;
|
||||
}
|
||||
|
||||
if (additionalFields.senderIcon) {
|
||||
//@ts-ignore
|
||||
message.hidden_data!.custom_icon = additionalFields.senderIcon as string;
|
||||
}
|
||||
|
||||
const body = {
|
||||
object: message,
|
||||
};
|
||||
|
||||
const endpoint = '/actions/message/save';
|
||||
|
||||
responseData = await twakeApiRequest.call(this, 'POST', endpoint, body);
|
||||
|
||||
responseData = responseData.object;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Array.isArray(responseData)) {
|
||||
returnData.push.apply(returnData, responseData as IDataObject[]);
|
||||
} else if (responseData !== undefined) {
|
||||
returnData.push(responseData as IDataObject);
|
||||
}
|
||||
return [this.helpers.returnJsonArray(returnData)];
|
||||
}
|
||||
}
|
BIN
packages/nodes-base/nodes/Twake/twake.png
Normal file
BIN
packages/nodes-base/nodes/Twake/twake.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.9 KiB |
|
@ -228,7 +228,7 @@ export const meetingFields = [
|
|||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Attendees register once and can attend any of the occurences',
|
||||
name: 'Attendees register once and can attend any of the occurrences',
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
|
@ -479,7 +479,7 @@ export const meetingFields = [
|
|||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Occurence ID',
|
||||
displayName: 'Occurrence ID',
|
||||
name: 'occurrenceId',
|
||||
type: 'string',
|
||||
default: '',
|
||||
|
@ -672,7 +672,7 @@ export const meetingFields = [
|
|||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Attendees register once and can attend any of the occurences',
|
||||
name: 'Attendees register once and can attend any of the occurrences',
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
|
|
|
@ -205,7 +205,7 @@ export const webinarFields = [
|
|||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Attendees register once and can attend any of the occurences',
|
||||
name: 'Attendees register once and can attend any of the occurrences',
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
|
@ -308,8 +308,8 @@ export const webinarFields = [
|
|||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Occurence ID',
|
||||
name: 'occurenceId',
|
||||
displayName: 'Occurrence ID',
|
||||
name: 'occurrenceId',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'To view webinar details of a particular occurrence of the recurring webinar.',
|
||||
|
|
|
@ -544,7 +544,7 @@ export class Zoom implements INodeType {
|
|||
// 'additionalFields',
|
||||
// i
|
||||
// ) as IDataObject;
|
||||
// if (additionalFields.occurenceId) {
|
||||
// if (additionalFields.occurrenceId) {
|
||||
// qs.occurrence_id = additionalFields.occurrenceId as string;
|
||||
// }
|
||||
// if (additionalFields.action) {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "n8n-nodes-base",
|
||||
"version": "0.70.1",
|
||||
"version": "0.71.0",
|
||||
"description": "Base nodes of n8n",
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"homepage": "https://n8n.io",
|
||||
|
@ -48,6 +48,7 @@
|
|||
"dist/credentials/CodaApi.credentials.js",
|
||||
"dist/credentials/CopperApi.credentials.js",
|
||||
"dist/credentials/CalendlyApi.credentials.js",
|
||||
"dist/credentials/CustomerIoApi.credentials.js",
|
||||
"dist/credentials/CrateDb.credentials.js",
|
||||
"dist/credentials/DisqusApi.credentials.js",
|
||||
"dist/credentials/DriftApi.credentials.js",
|
||||
|
@ -98,6 +99,8 @@
|
|||
"dist/credentials/MattermostApi.credentials.js",
|
||||
"dist/credentials/MauticApi.credentials.js",
|
||||
"dist/credentials/MauticOAuth2Api.credentials.js",
|
||||
"dist/credentials/MediumApi.credentials.js",
|
||||
"dist/credentials/MediumOAuth2Api.credentials.js",
|
||||
"dist/credentials/MessageBirdApi.credentials.js",
|
||||
"dist/credentials/MicrosoftExcelOAuth2Api.credentials.js",
|
||||
"dist/credentials/MicrosoftOAuth2Api.credentials.js",
|
||||
|
@ -141,12 +144,15 @@
|
|||
"dist/credentials/SurveyMonkeyOAuth2Api.credentials.js",
|
||||
"dist/credentials/TelegramApi.credentials.js",
|
||||
"dist/credentials/TodoistApi.credentials.js",
|
||||
"dist/credentials/TravisCiApi.credentials.js",
|
||||
"dist/credentials/TrelloApi.credentials.js",
|
||||
"dist/credentials/TwilioApi.credentials.js",
|
||||
"dist/credentials/TwitterOAuth1Api.credentials.js",
|
||||
"dist/credentials/TypeformApi.credentials.js",
|
||||
"dist/credentials/TypeformOAuth2Api.credentials.js",
|
||||
"dist/credentials/TogglApi.credentials.js",
|
||||
"dist/credentials/TwakeCloudApi.credentials.js",
|
||||
"dist/credentials/TwakeServerApi.credentials.js",
|
||||
"dist/credentials/UpleadApi.credentials.js",
|
||||
"dist/credentials/VeroApi.credentials.js",
|
||||
"dist/credentials/WebflowApi.credentials.js",
|
||||
|
@ -197,6 +203,7 @@
|
|||
"dist/nodes/CrateDb/CrateDb.node.js",
|
||||
"dist/nodes/Cron.node.js",
|
||||
"dist/nodes/Crypto.node.js",
|
||||
"dist/nodes/CustomerIo/CustomerIoTrigger.node.js",
|
||||
"dist/nodes/DateTime.node.js",
|
||||
"dist/nodes/Discord/Discord.node.js",
|
||||
"dist/nodes/Disqus/Disqus.node.js",
|
||||
|
@ -256,6 +263,7 @@
|
|||
"dist/nodes/Mattermost/Mattermost.node.js",
|
||||
"dist/nodes/Mautic/Mautic.node.js",
|
||||
"dist/nodes/Mautic/MauticTrigger.node.js",
|
||||
"dist/nodes/Medium/Medium.node.js",
|
||||
"dist/nodes/Merge.node.js",
|
||||
"dist/nodes/MessageBird/MessageBird.node.js",
|
||||
"dist/nodes/Microsoft/Excel/MicrosoftExcel.node.js",
|
||||
|
@ -309,11 +317,13 @@
|
|||
"dist/nodes/Telegram/TelegramTrigger.node.js",
|
||||
"dist/nodes/Todoist/Todoist.node.js",
|
||||
"dist/nodes/Toggl/TogglTrigger.node.js",
|
||||
"dist/nodes/TravisCi/TravisCi.node.js",
|
||||
"dist/nodes/Trello/Trello.node.js",
|
||||
"dist/nodes/Trello/TrelloTrigger.node.js",
|
||||
"dist/nodes/Twilio/Twilio.node.js",
|
||||
"dist/nodes/Twitter/Twitter.node.js",
|
||||
"dist/nodes/Typeform/TypeformTrigger.node.js",
|
||||
"dist/nodes/Twake/Twake.node.js",
|
||||
"dist/nodes/Uplead/Uplead.node.js",
|
||||
"dist/nodes/Vero/Vero.node.js",
|
||||
"dist/nodes/Webflow/WebflowTrigger.node.js",
|
||||
|
@ -355,7 +365,7 @@
|
|||
"@types/xml2js": "^0.4.3",
|
||||
"gulp": "^4.0.0",
|
||||
"jest": "^24.9.0",
|
||||
"n8n-workflow": "~0.36.0",
|
||||
"n8n-workflow": "~0.37.0",
|
||||
"ts-jest": "^24.0.2",
|
||||
"tslint": "^5.17.0",
|
||||
"typescript": "~3.7.4"
|
||||
|
@ -382,7 +392,7 @@
|
|||
"mongodb": "^3.5.5",
|
||||
"mssql": "^6.2.0",
|
||||
"mysql2": "^2.0.1",
|
||||
"n8n-core": "~0.40.0",
|
||||
"n8n-core": "~0.41.0",
|
||||
"nodemailer": "^6.4.6",
|
||||
"pdf-parse": "^1.1.1",
|
||||
"pg": "^8.3.0",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "n8n-workflow",
|
||||
"version": "0.36.0",
|
||||
"version": "0.37.0",
|
||||
"description": "Workflow base code of n8n",
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"homepage": "https://n8n.io",
|
||||
|
|
Loading…
Reference in a new issue