From 896e64f8d2dafa0181520452df019efddd39d48b Mon Sep 17 00:00:00 2001 From: d3no Date: Wed, 12 Feb 2020 15:52:36 +0800 Subject: [PATCH 1/3] Commit Mocean node --- .../nodes/Mocean/GenericFunctions.ts | 68 +++++ .../nodes-base/nodes/Mocean/Mocean.node.ts | 233 ++++++++++++++++++ .../nodes/Mocean/MoceanApi.credentials.ts | 27 ++ packages/nodes-base/nodes/Mocean/logo.png | Bin 0 -> 1833 bytes 4 files changed, 328 insertions(+) create mode 100644 packages/nodes-base/nodes/Mocean/GenericFunctions.ts create mode 100644 packages/nodes-base/nodes/Mocean/Mocean.node.ts create mode 100644 packages/nodes-base/nodes/Mocean/MoceanApi.credentials.ts create mode 100644 packages/nodes-base/nodes/Mocean/logo.png diff --git a/packages/nodes-base/nodes/Mocean/GenericFunctions.ts b/packages/nodes-base/nodes/Mocean/GenericFunctions.ts new file mode 100644 index 000000000..4dfdf415e --- /dev/null +++ b/packages/nodes-base/nodes/Mocean/GenericFunctions.ts @@ -0,0 +1,68 @@ +import { + IExecuteFunctions, + IHookFunctions, +} from 'n8n-core'; + +import { + IDataObject, +} from 'n8n-workflow'; + +/** + * Make an API request to Twilio + * + * @param {IHookFunctions} this + * @param {string} method + * @param {string} url + * @param {object} body + * @returns {Promise} + */ +export async function moceanApiRequest(this: IHookFunctions | IExecuteFunctions, method: string, endpoint: string, body: IDataObject, query?: IDataObject): Promise { // tslint:disable-line:no-any + const credentials = this.getCredentials('moceanApi'); + if (credentials === undefined) { + throw new Error('No credentials got returned!'); + } + + if (query === undefined) { + query = {}; + } + + if (method === 'POST') { + body['mocean-api-key'] = credentials['mocean-api-key']; + body['mocean-api-secret'] = credentials['mocean-api-secret']; + body['mocean-resp-format'] = 'JSON'; + } else if(method === 'GET') { + query['mocean-api-key'] = credentials['mocean-api-key']; + query['mocean-api-secret'] = credentials['mocean-api-secret']; + query['mocean-resp-format'] = 'JSON'; + } + console.log(body); + const options = { + method, + form: body, + qs: query, + uri: `https://rest.moceanapi.com${endpoint}`, + json: true + }; + + try { + return await this.helpers.request(options); + } catch (error) { + if (error.statusCode === 401) { + // Return a clear error + throw new Error('Authentication fail.'); + } + + if (error.response && error.response.body && error.response.body.message) { + // Try to return the error prettier + let errorMessage = `Twilio error response [${error.statusCode}]: ${error.response.body.message}`; + if (error.response.body.more_info) { + errorMessage = `errorMessage (${error.response.body.more_info})`; + } + + throw new Error(errorMessage); + } + + // If that data does not exist for some reason return the actual error + throw error; + } +} \ No newline at end of file diff --git a/packages/nodes-base/nodes/Mocean/Mocean.node.ts b/packages/nodes-base/nodes/Mocean/Mocean.node.ts new file mode 100644 index 000000000..33a2b1fd9 --- /dev/null +++ b/packages/nodes-base/nodes/Mocean/Mocean.node.ts @@ -0,0 +1,233 @@ +import { IExecuteFunctions } from 'n8n-core'; +import { + IDataObject, + INodeExecutionData, + INodeType, + INodeTypeDescription, +} from 'n8n-workflow'; + +import {moceanApiRequest} from './GenericFunctions'; + + +export class Mocean implements INodeType { + description: INodeTypeDescription = { + displayName: 'Mocean', + name: 'mocean', + icon: 'file:logo.png', + group: ['transform'], + version: 1, + description: 'This is the official node created from Mocean', + defaults: { + name: 'Mocean', + color: '#772244', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: "moceanApi", + required: true + } + ], + properties: [ + // Node properties which the user gets displayed and + // can change on the node. + { + displayName: 'Resource', + name: 'resource', + type: 'options', + options:[ + { + name: "SMS", + value: "sms" + }, + { + name: "Voice", + value: "voice" + } + ], + default: 'sms', + }, + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'sms', + 'voice' + ], + }, + }, + options: [ + { + name: 'Send', + value: 'send', + description: 'Send SMS/Voice message', + }, + ], + default: 'send', + description: 'The operation to perform.', + }, + { + displayName: 'From', + name: 'from', + type: 'string', + default: '', + placeholder: 'Sender Number', + required: true, + displayOptions: { + show: { + operation: [ + 'send', + ], + resource: [ + 'sms', + 'voice', + ], + }, + }, + description: 'The number to which to send the message', + }, + + { + displayName: 'To', + name: 'to', + type: 'string', + default: '', + placeholder: 'Receipient number', + required: true, + displayOptions: { + show: { + operation: [ + 'send', + ], + resource: [ + 'sms', + 'voice', + ], + }, + }, + description: 'The number from which to send the message', + }, + + { + displayName: 'Language', + name: 'language', + type: 'options', + options:[ + { + name: "English (United States)", + value: "en-US" + }, + { + name: "English (United Kingdom)", + value: "en-GB" + }, + { + name: "Chinese Mandarin (China", + value: "cmn-CN" + }, + { + name: "Japanese (Japan)", + value: "ja-JP" + }, + { + name: "Korean (Korea)", + value: "ko-KR" + } + ], + displayOptions: { + show: { + operation: [ + 'send', + ], + resource: [ + 'voice', + ], + }, + }, + default: 'en-US', + }, + + { + displayName: 'Message', + name: 'message', + type: 'string', + default: '', + placeholder: '', + required: true, + displayOptions: { + show: { + operation: [ + 'send', + ], + resource: [ + 'sms', + 'voice', + ], + }, + }, + description: 'The message to send', + }, + ], + + }; + + + async execute(this: IExecuteFunctions): Promise { + + const items = this.getInputData(); + let item: INodeExecutionData; + let returnData: IDataObject[] = []; + + let endpoint: string; + let operation: string; + let requesetMethod: string; + let resource: string; + let text: string; + // For Post + let body: IDataObject; + // For Query string + let qs: IDataObject; + + + for (let itemIndex = 0; itemIndex < items.length; itemIndex++) { + body = {}; + qs = {}; + + resource = this.getNodeParameter('resource', itemIndex, '') as string; + operation = this.getNodeParameter('operation',itemIndex,'') as string; + text = this.getNodeParameter('message', itemIndex, '') as string; + requesetMethod = 'POST'; + body['mocean-from'] = this.getNodeParameter('from', itemIndex, '') as string; + body['mocean-to'] = this.getNodeParameter('to', itemIndex, '') as string; + + + if (resource === 'voice') { + let language: string = this.getNodeParameter("language",itemIndex) as string; + let command:any = [{"action": "say", "language": `${language}`, "text": `${text}`}]; + + body['mocean-command'] = JSON.stringify(command); + endpoint = '/rest/2/voice/dial'; + } else if(resource === 'sms') { + body['mocean-text'] = text; + endpoint = '/rest/2/sms'; + } else { + throw new Error(`Unknown resource ${resource}`); + } + + if (operation === "send") { + const responseData = await moceanApiRequest.call(this,requesetMethod,endpoint,body,qs); + returnData.push(responseData as IDataObject); + } else { + throw new Error(`Unknown operation ${operation}`); + } + + + } + + return [this.helpers.returnJsonArray(returnData)]; + } +} diff --git a/packages/nodes-base/nodes/Mocean/MoceanApi.credentials.ts b/packages/nodes-base/nodes/Mocean/MoceanApi.credentials.ts new file mode 100644 index 000000000..fb46b2d22 --- /dev/null +++ b/packages/nodes-base/nodes/Mocean/MoceanApi.credentials.ts @@ -0,0 +1,27 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + + +export class MoceanApi implements ICredentialType { + name = 'moceanApi'; + displayName = 'Mocean Api'; + properties = [ + // The credentials to get from user and save encrypted. + // Properties can be defined exactly in the same way + // as node properties. + { + displayName: 'API Key', + name: 'mocean-api-key', + type: 'string' as NodePropertyTypes, + default: '', + }, + { + displayName: 'API Secret', + name: 'mocean-api-secret', + type: 'string' as NodePropertyTypes, + default: '', + }, + ]; +} diff --git a/packages/nodes-base/nodes/Mocean/logo.png b/packages/nodes-base/nodes/Mocean/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..5368d5f0b4c05e4dd27ad11560c880b384f60248 GIT binary patch literal 1833 zcmV+^2iEwBP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!~g&e!~vBn z4jTXf0AG4kSaechcOY9y8Ey_$*C@9KL%gjmT zQUC*E6D|`=6HB8+0|NsK0|+oQFfcX*l14zbIaDao00o$so23DvxmmKADTvPn08hFV zXd5lqi~s-zLrFwIRA@u(T4`)lRTTba=}e)EP?jRQfwt0u(t>P3p|Y6@f}#k${lA%fZW`q022taliM^$vru-eC~dI}E~lhe25HFbL}% z24TI!Agp&7g!K-Cu-@^|5jM?Ulj~iuIS)3g88NNGaeB>Q-;#!)ft$EyL3OnYD`s@Z zq#@C2e(CoGn6oYy6_+a1d+_sdPex+v+}?<5+d!KmF5alZl%+X1bmmVuYTYoI09Ty{ zQ7yuezfODB#=XT@^4(dSyIcYPzXhtQTv$A%E2a+<2 zTLC66IfB)@is1Elv2uD3OdJxW=Jy`Ih5_&I0r*VMp)oMmxjjIQ8*UorAYF>T8v#x_SrBD|b{N1j9c%faIC`aN_h$R9=;JZGbjql}=^jN9yWa^&Gj(T76Q@ z`QdbV;FW-y&6t#$<(s9Y*pnX|b?V-1HuGvR!6L78d|Adh&5~ID1MfqFv^Cn;NzPU0 z#>UNza)V&RcW4Z6&RBR4zJcqEI3ymCp?GfAKK1$D@9hSfg~A#fg#2y8;XF1Lj-wnW z#-n`K2o#h2_U#40BX%V9ZG)TpUPt-C(J0Ltja$dZVfLtwY9GOt{l5beaQ>O=S&{b5 z8Q+Qq#e>5lMM^)%FFLH|#b=q<7I>?!gvy=Wie*C5hxM67F!KXSW*osb3+2ZLhueWW zPVD)jpJqrzM%2TC3Gvjc8zq#vWYAki1yFt~r*%^@58^{*4;f~LJB<<)vS7Jnewv6T z3}SeQO-Z#8pJAt+A2ZWV@ao0pIlZ+Bl32B?P`meoDs9;`M3vWCN?%H+o?Z4(>=ZP@ zbaHTy7D_IKr*EjK+@jmck&s|3y2mzVbUm;z%jcODhEvrS_9gWy z?-QaM`7G{7;T6_DgJ5eI3{#i2FeR+HH=>tg-uhF@u88u!TtI*5_o!#vB4uQ(BAlXv z5CAG5szn&4zsmJs3leAijMkA1Kmatt+(@GTN}tv$I-7pEfb^e=)u!W;+M}BF>xX3K z-(gY>N=1-+*R$Of(tW6P(#F4?WK-nlAAD~WDH1bQl2}L(9%taC2}LqMQex%o2|4UD zfw);#gT<4(Fxx=(;Vby|*NfbXf%oCACt~={&=_@}wzH7Zy+CKZn9OchBF%}ONz-cA zJWOR9R@2{_N?viP3YavwL0unZCt;xIn7=_ih&HTu$%^xK4ZMfng!kx}dn11$<|RL& z>~>JP*7`v>d%g?!w<%%%9oslJW(WJQJtQ|w93mVdJsQ@s9;J~C8OpPm0ok8(4W^#y2;os= z@0G4>aJ>>4^ZTgJWi09s+^EDQ9y5{sH^CIo+LX~L4dH{gqGxOq^m>M6sK&PU%l_ESW_fbe^MELiQvaB>9x7Sn^IZYQ>W{Sdlse#H zGn0lUFgxXp<*2CAbzX4-YCCJU<3K)f_RMBHgRtIV5Y{^k!g_~6Snn_h>m3GRy~7}^ZvcM* Xq(*?jdKNqp00000NkvXXu0mjfPl00L literal 0 HcmV?d00001 From 7f203d6d817035cae892ec86606edf5b5cc83a5e Mon Sep 17 00:00:00 2001 From: d3no Date: Wed, 12 Feb 2020 16:19:53 +0800 Subject: [PATCH 2/3] Re commmit --- .../{nodes/Mocean => credentials}/MoceanApi.credentials.ts | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename packages/nodes-base/{nodes/Mocean => credentials}/MoceanApi.credentials.ts (100%) diff --git a/packages/nodes-base/nodes/Mocean/MoceanApi.credentials.ts b/packages/nodes-base/credentials/MoceanApi.credentials.ts similarity index 100% rename from packages/nodes-base/nodes/Mocean/MoceanApi.credentials.ts rename to packages/nodes-base/credentials/MoceanApi.credentials.ts From 77521bb387732db4770293e73271a94e7acc4ad8 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 14 Feb 2020 21:39:53 -0800 Subject: [PATCH 3/3] :zap: Improvements and fixes on Mocean-Node --- .../nodes/Mocean/GenericFunctions.ts | 28 +++---- .../nodes-base/nodes/Mocean/Mocean.node.ts | 78 ++++++++++--------- packages/nodes-base/package.json | 10 ++- 3 files changed, 63 insertions(+), 53 deletions(-) diff --git a/packages/nodes-base/nodes/Mocean/GenericFunctions.ts b/packages/nodes-base/nodes/Mocean/GenericFunctions.ts index 4dfdf415e..60f537377 100644 --- a/packages/nodes-base/nodes/Mocean/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Mocean/GenericFunctions.ts @@ -24,24 +24,24 @@ export async function moceanApiRequest(this: IHookFunctions | IExecuteFunctions, if (query === undefined) { query = {}; - } - - if (method === 'POST') { - body['mocean-api-key'] = credentials['mocean-api-key']; + } + + if (method === 'POST') { + body['mocean-api-key'] = credentials['mocean-api-key']; body['mocean-api-secret'] = credentials['mocean-api-secret']; body['mocean-resp-format'] = 'JSON'; - } else if(method === 'GET') { - query['mocean-api-key'] = credentials['mocean-api-key']; + } else if (method === 'GET') { + query['mocean-api-key'] = credentials['mocean-api-key']; query['mocean-api-secret'] = credentials['mocean-api-secret']; query['mocean-resp-format'] = 'JSON'; - } - console.log(body); + } + const options = { method, form: body, qs: query, uri: `https://rest.moceanapi.com${endpoint}`, - json: true + json: true, }; try { @@ -49,20 +49,20 @@ export async function moceanApiRequest(this: IHookFunctions | IExecuteFunctions, } catch (error) { if (error.statusCode === 401) { // Return a clear error - throw new Error('Authentication fail.'); + throw new Error('Authentication failed.'); } if (error.response && error.response.body && error.response.body.message) { // Try to return the error prettier - let errorMessage = `Twilio error response [${error.statusCode}]: ${error.response.body.message}`; + let errorMessage = error.response.body.message; if (error.response.body.more_info) { - errorMessage = `errorMessage (${error.response.body.more_info})`; + errorMessage += `errorMessage (${error.response.body.more_info})`; } - throw new Error(errorMessage); + throw new Error(`Mocean error response [${error.statusCode}]: ${errorMessage}`); } // If that data does not exist for some reason return the actual error throw error; } -} \ No newline at end of file +} diff --git a/packages/nodes-base/nodes/Mocean/Mocean.node.ts b/packages/nodes-base/nodes/Mocean/Mocean.node.ts index 33a2b1fd9..0776e6f75 100644 --- a/packages/nodes-base/nodes/Mocean/Mocean.node.ts +++ b/packages/nodes-base/nodes/Mocean/Mocean.node.ts @@ -13,10 +13,10 @@ export class Mocean implements INodeType { description: INodeTypeDescription = { displayName: 'Mocean', name: 'mocean', - icon: 'file:logo.png', + icon: 'file:mocean.png', group: ['transform'], version: 1, - description: 'This is the official node created from Mocean', + description: 'Send SMS & voice messages via Mocean (https://moceanapi.com)', defaults: { name: 'Mocean', color: '#772244', @@ -25,8 +25,8 @@ export class Mocean implements INodeType { outputs: ['main'], credentials: [ { - name: "moceanApi", - required: true + name: 'moceanApi', + required: true, } ], properties: [ @@ -38,12 +38,12 @@ export class Mocean implements INodeType { type: 'options', options:[ { - name: "SMS", - value: "sms" + name: 'SMS', + value: 'sms', }, { - name: "Voice", - value: "voice" + name: 'Voice', + value: 'voice', } ], default: 'sms', @@ -56,7 +56,7 @@ export class Mocean implements INodeType { show: { resource: [ 'sms', - 'voice' + 'voice', ], }, }, @@ -118,25 +118,25 @@ export class Mocean implements INodeType { type: 'options', options:[ { - name: "English (United States)", - value: "en-US" + name: 'Chinese Mandarin (China)', + value: 'cmn-CN' }, { - name: "English (United Kingdom)", - value: "en-GB" + name: 'English (United Kingdom)', + value: 'en-GB' }, { - name: "Chinese Mandarin (China", - value: "cmn-CN" + name: 'English (United States)', + value: 'en-US' }, { - name: "Japanese (Japan)", - value: "ja-JP" + name: 'Japanese (Japan)', + value: 'ja-JP' }, { - name: "Korean (Korea)", - value: "ko-KR" - } + name: 'Korean (Korea)', + value: 'ko-KR' + }, ], displayOptions: { show: { @@ -172,27 +172,25 @@ export class Mocean implements INodeType { description: 'The message to send', }, ], - + }; async execute(this: IExecuteFunctions): Promise { - const items = this.getInputData(); - let item: INodeExecutionData; - let returnData: IDataObject[] = []; - + const returnData: IDataObject[] = []; + let endpoint: string; let operation: string; let requesetMethod: string; let resource: string; let text: string; + let dataKey: string; // For Post let body: IDataObject; // For Query string let qs: IDataObject; - for (let itemIndex = 0; itemIndex < items.length; itemIndex++) { body = {}; qs = {}; @@ -203,29 +201,39 @@ export class Mocean implements INodeType { requesetMethod = 'POST'; body['mocean-from'] = this.getNodeParameter('from', itemIndex, '') as string; body['mocean-to'] = this.getNodeParameter('to', itemIndex, '') as string; - - + if (resource === 'voice') { - let language: string = this.getNodeParameter("language",itemIndex) as string; - let command:any = [{"action": "say", "language": `${language}`, "text": `${text}`}]; - + const language: string = this.getNodeParameter('language', itemIndex) as string; + const command = [ + { + action: 'say', + language, + text, + } + ]; + + dataKey = 'voice'; body['mocean-command'] = JSON.stringify(command); endpoint = '/rest/2/voice/dial'; } else if(resource === 'sms') { + dataKey = 'messages'; body['mocean-text'] = text; endpoint = '/rest/2/sms'; } else { throw new Error(`Unknown resource ${resource}`); } - if (operation === "send") { + if (operation === 'send') { const responseData = await moceanApiRequest.call(this,requesetMethod,endpoint,body,qs); - returnData.push(responseData as IDataObject); + + for (const item of responseData[dataKey] as IDataObject[]) { + item.type = resource; + returnData.push(item); + } + } else { throw new Error(`Unknown operation ${operation}`); } - - } return [this.helpers.returnJsonArray(returnData)]; diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 1ccea2b65..150c7e133 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -63,14 +63,15 @@ "dist/credentials/LinkFishApi.credentials.js", "dist/credentials/MailchimpApi.credentials.js", "dist/credentials/MailgunApi.credentials.js", + "dist/credentials/MailjetEmailApi.credentials.js", + "dist/credentials/MailjetSmsApi.credentials.js", "dist/credentials/MandrillApi.credentials.js", "dist/credentials/MattermostApi.credentials.js", "dist/credentials/MauticApi.credentials.js", + "dist/credentials/MoceanApi.credentials.js", "dist/credentials/MongoDb.credentials.js", "dist/credentials/Msg91Api.credentials.js", "dist/credentials/MySql.credentials.js", - "dist/credentials/MailjetEmailApi.credentials.js", - "dist/credentials/MailjetSmsApi.credentials.js", "dist/credentials/NextCloudApi.credentials.js", "dist/credentials/OpenWeatherMapApi.credentials.js", "dist/credentials/PayPalApi.credentials.js", @@ -160,17 +161,18 @@ "dist/nodes/Mailchimp/Mailchimp.node.js", "dist/nodes/Mailchimp/MailchimpTrigger.node.js", "dist/nodes/Mailgun/Mailgun.node.js", + "dist/nodes/Mailjet/Mailjet.node.js", + "dist/nodes/Mailjet/MailjetTrigger.node.js", "dist/nodes/Mandrill/Mandrill.node.js", "dist/nodes/Mattermost/Mattermost.node.js", "dist/nodes/Mautic/Mautic.node.js", "dist/nodes/Mautic/MauticTrigger.node.js", "dist/nodes/Merge.node.js", + "dist/nodes/Mocean/Mocean.node.js", "dist/nodes/MongoDb/MongoDb.node.js", "dist/nodes/MoveBinaryData.node.js", "dist/nodes/Msg91/Msg91.node.js", "dist/nodes/MySql/MySql.node.js", - "dist/nodes/Mailjet/Mailjet.node.js", - "dist/nodes/Mailjet/MailjetTrigger.node.js", "dist/nodes/NextCloud/NextCloud.node.js", "dist/nodes/NoOp.node.js", "dist/nodes/OpenWeatherMap.node.js",