diff --git a/packages/nodes-base/credentials/LingvaNexApi.credentials.ts b/packages/nodes-base/credentials/LingvaNexApi.credentials.ts new file mode 100644 index 0000000000..87265f98ee --- /dev/null +++ b/packages/nodes-base/credentials/LingvaNexApi.credentials.ts @@ -0,0 +1,18 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + +export class LingvaNexApi implements ICredentialType { + name = 'lingvaNexApi'; + displayName = 'LingvaNex API'; + documentationUrl = 'lingvaNex'; + properties = [ + { + displayName: 'API Key', + name: 'apiKey', + type: 'string' as NodePropertyTypes, + default: '', + }, + ]; +} diff --git a/packages/nodes-base/nodes/LingvaNex/ActivityDescription.ts b/packages/nodes-base/nodes/LingvaNex/ActivityDescription.ts new file mode 100644 index 0000000000..fad53ff169 --- /dev/null +++ b/packages/nodes-base/nodes/LingvaNex/ActivityDescription.ts @@ -0,0 +1,248 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +export const activityOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'activity', + ], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create an activity for a member', + }, + { + name: 'Get All', + value: 'getAll', + description: 'Get all activities', + }, + ], + default: 'create', + description: 'The operation to perform.', + }, +] as INodeProperties[]; + +export const activityFields = [ + + /* -------------------------------------------------------------------------- */ + /* activity:create */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Workspace', + name: 'workspaceId', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getWorkspaces', + }, + default: '', + required: true, + displayOptions: { + show: { + resource: [ + 'activity', + ], + operation: [ + 'create', + ], + }, + }, + }, + { + displayName: 'Member ID', + name: 'memberId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + resource: [ + 'activity', + ], + operation: [ + 'create', + ], + }, + }, + }, + { + displayName: 'Title', + name: 'title', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + resource: [ + 'activity', + ], + operation: [ + 'create', + ], + }, + }, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + displayOptions: { + show: { + resource: [ + 'activity', + ], + operation: [ + 'create', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Activity Type', + name: 'activityType', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getActivityTypes', + }, + default: '', + description: 'A user-defined way to group activities of the same nature', + }, + { + displayName: 'Description', + name: 'description', + type: 'string', + default: '', + description: 'A description of the activity; displayed in the timeline', + }, + { + displayName: 'Key', + name: 'key', + type: 'string', + default: '', + description: 'Supply a key that must be unique or leave blank to have one generated', + }, + { + displayName: 'Link', + name: 'link', + type: 'string', + default: '', + description: 'A URL for the activity; displayed in the timeline', + }, + { + displayName: 'Link Text', + name: 'linkText', + type: 'string', + default: '', + description: 'The text for the timeline link', + }, + { + displayName: 'Occurred At', + name: 'occurredAt', + type: 'dateTime', + default: '', + description: 'The date and time the activity occurred; defaults to now', + }, + ], + }, + + /* -------------------------------------------------------------------------- */ + /* activity:getAll */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Workspace', + name: 'workspaceId', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getWorkspaces', + }, + default: '', + required: true, + displayOptions: { + show: { + resource: [ + 'activity', + ], + operation: [ + 'getAll', + ], + }, + }, + }, + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'activity', + ], + }, + }, + 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: [ + 'activity', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 500, + }, + default: 100, + description: 'How many results to return.', + }, + { + displayName: 'Filters', + name: 'filters', + type: 'collection', + placeholder: 'Add Filter', + default: {}, + displayOptions: { + show: { + resource: [ + 'activity', + ], + operation: [ + 'getAll', + ], + }, + }, + options: [ + { + displayName: 'Member ID', + name: 'memberId', + type: 'string', + default: '', + description: 'When set the post will be filtered by the member id.', + }, + ], + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/LingvaNex/GenericFunctions.ts b/packages/nodes-base/nodes/LingvaNex/GenericFunctions.ts new file mode 100644 index 0000000000..e553c56296 --- /dev/null +++ b/packages/nodes-base/nodes/LingvaNex/GenericFunctions.ts @@ -0,0 +1,47 @@ +import { + OptionsWithUri, +} from 'request'; + +import { + IExecuteFunctions, + IExecuteSingleFunctions, + IHookFunctions, + ILoadOptionsFunctions, +} from 'n8n-core'; + +import { + IDataObject, +} from 'n8n-workflow'; + +export async function lingvaNexApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any + try { + const credentials = this.getCredentials('lingvaNexApi'); + if (credentials === undefined) { + throw new Error('No credentials got returned!'); + } + let options: OptionsWithUri = { + headers: { + Authorization: `Bearer ${credentials.apiKey}`, + }, + method, + qs, + body, + uri: uri || `https://api-b2b.backenster.com/b1/api/v3${resource}`, + json: true, + }; + + options = Object.assign({}, options, option); + + return await this.helpers.request!(options); + } catch (error) { + + if (error.response && error.response.body && error.response.body.message) { + // Try to return the error prettier + const errorBody = error.response.body; + throw new Error(`LingvaNex error response [${error.statusCode}]: ${errorBody.message}`); + } + + // Expected error data did not get returned so throw the actual error + throw error; + } +} diff --git a/packages/nodes-base/nodes/LingvaNex/LingvaNex.node.ts b/packages/nodes-base/nodes/LingvaNex/LingvaNex.node.ts new file mode 100644 index 0000000000..8740026919 --- /dev/null +++ b/packages/nodes-base/nodes/LingvaNex/LingvaNex.node.ts @@ -0,0 +1,183 @@ +import { + IExecuteFunctions, +} from 'n8n-core'; + +import { + IDataObject, + ILoadOptionsFunctions, + INodeExecutionData, + INodePropertyOptions, + INodeType, + INodeTypeDescription, +} from 'n8n-workflow'; + +import { + lingvaNexApiRequest, +} from './GenericFunctions'; + +export class LingvaNex implements INodeType { + description: INodeTypeDescription = { + displayName: 'LingvaNex', + name: 'lingvaNex', + icon: 'file:lingvanex.png', + group: ['output'], + version: 1, + subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', + description: 'Consume LingvaNex API', + defaults: { + name: 'LingvaNex', + color: '#00ade8', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'lingvaNexApi', + required: true, + }, + ], + properties: [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + options: [ + { + name: 'Translate', + value: 'translate', + description: 'Translate data', + }, + ], + default: 'translate', + description: 'The operation to perform', + }, + // ---------------------------------- + // All + // ---------------------------------- + { + displayName: 'Text', + name: 'text', + type: 'string', + default: '', + description: 'The input text to translate', + required: true, + displayOptions: { + show: { + operation: [ + 'translate', + ], + }, + }, + }, + { + displayName: 'Translate To', + name: 'translateTo', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getLanguages', + }, + default: '', + description: 'The language to use for translation of the input text, set to one of the
language codes listed in Language Support', + required: true, + displayOptions: { + show: { + operation: [ + 'translate', + ], + }, + }, + }, + { + displayName: 'Additional Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + displayOptions: { + show: { + operation: [ + 'translate', + ], + }, + }, + options: [ + { + displayName: 'From', + name: 'from', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getLanguages', + }, + default: '', + description: 'The language code in the format “language code_code of the country”. If this parameter is not present, the auto-detect language mode is enabled.', + }, + { + displayName: 'Platform', + name: 'platform', + type: 'string', + default: 'api', + description: '', + }, + { + displayName: 'Translate Mode', + name: 'translateMode', + type: 'string', + default: '', + description: 'Describe the input text format. Possible value is "html" for translating and preserving html structure.
If value is not specified or is other than "html" than plain text is translating.', + }, + ], + }, + ], + }; + + methods = { + loadOptions: { + async getLanguages( + this: ILoadOptionsFunctions, + ): Promise { + const returnData: INodePropertyOptions[] = []; + const data = await lingvaNexApiRequest.call( + this, + 'GET', + '/getLanguages', + {}, + {'platform': 'api'}, + ); + for (const language of data.result) { + returnData.push({ + name: language.englishName, + value: language.full_code, + }); + } + return returnData; + }, + }, + }; + + async execute(this: IExecuteFunctions): Promise { + const items = this.getInputData(); + const length = items.length as unknown as number; + + const operation = this.getNodeParameter('operation', 0) as string; + const responseData = []; + for (let i = 0; i < length; i++) { + if (operation === 'translate') { + const text = this.getNodeParameter('text', i) as string; + const translateTo = this.getNodeParameter('translateTo', i) as string; + const options = this.getNodeParameter('options', i) as IDataObject; + + const body: IDataObject = { + data: text, + to: translateTo, + platform: 'api', + }; + + Object.assign(body, options); + + const response = await lingvaNexApiRequest.call(this, 'POST', `/translate`, body); + responseData.push(response); + } + } + return [this.helpers.returnJsonArray(responseData)]; + } +} diff --git a/packages/nodes-base/nodes/LingvaNex/lingvanex.png b/packages/nodes-base/nodes/LingvaNex/lingvanex.png new file mode 100644 index 0000000000..4b65138854 Binary files /dev/null and b/packages/nodes-base/nodes/LingvaNex/lingvanex.png differ diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index ef3237efbb..2793e2ecd2 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -113,6 +113,7 @@ "dist/credentials/Kafka.credentials.js", "dist/credentials/KeapOAuth2Api.credentials.js", "dist/credentials/LineNotifyOAuth2Api.credentials.js", + "dist/credentials/LingvaNexApi.credentials.js", "dist/credentials/LinkedInOAuth2Api.credentials.js", "dist/credentials/MailerLiteApi.credentials.js", "dist/credentials/MailchimpApi.credentials.js", @@ -328,6 +329,7 @@ "dist/nodes/Keap/Keap.node.js", "dist/nodes/Keap/KeapTrigger.node.js", "dist/nodes/Line/Line.node.js", + "dist/nodes/LingvaNex/LingvaNex.node.js", "dist/nodes/LinkedIn/LinkedIn.node.js", "dist/nodes/MailerLite/MailerLite.node.js", "dist/nodes/MailerLite/MailerLiteTrigger.node.js",