diff --git a/packages/nodes-base/credentials/PlivoApi.credentials.ts b/packages/nodes-base/credentials/PlivoApi.credentials.ts new file mode 100644 index 0000000000..c07162b283 --- /dev/null +++ b/packages/nodes-base/credentials/PlivoApi.credentials.ts @@ -0,0 +1,24 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + +export class PlivoApi implements ICredentialType { + name = 'plivoApi'; + displayName = 'Plivo API'; + documentationUrl = 'plivo'; + properties = [ + { + displayName: 'Auth ID', + name: 'authId', + type: 'string' as NodePropertyTypes, + default: '', + }, + { + displayName: 'Auth Token', + name: 'authToken', + type: 'string' as NodePropertyTypes, + default: '', + }, + ]; +} diff --git a/packages/nodes-base/nodes/Plivo/CallDescription.ts b/packages/nodes-base/nodes/Plivo/CallDescription.ts new file mode 100644 index 0000000000..7db6e2e19f --- /dev/null +++ b/packages/nodes-base/nodes/Plivo/CallDescription.ts @@ -0,0 +1,117 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +export const callOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'call', + ], + }, + }, + options: [ + { + name: 'Make', + value: 'make', + description: 'Make a voice call', + }, + ], + default: 'make', + description: 'Operation to perform.', + }, +] as INodeProperties[]; + +export const callFields = [ + // ---------------------------------- + // call: make + // ---------------------------------- + { + displayName: 'From', + name: 'from', + type: 'string', + default: '', + placeholder: '+14156667777', + description: 'Caller ID for the call to make.', + required: true, + displayOptions: { + show: { + resource: [ + 'call', + ], + operation: [ + 'make', + ], + }, + }, + }, + { + displayName: 'To', + name: 'to', + type: 'string', + default: '', + placeholder: '+14156667778', + required: true, + description: 'Phone number to make the call to.', + displayOptions: { + show: { + resource: [ + 'call', + ], + operation: [ + 'make', + ], + }, + }, + }, + { + displayName: 'Answer Method', + name: 'answer_method', + type: 'options', + required: true, + description: 'HTTP verb to be used when invoking the Answer URL.', + default: 'POST', + options: [ + { + name: 'GET', + value: 'GET', + }, + { + name: 'POST', + value: 'POST', + }, + ], + displayOptions: { + show: { + resource: [ + 'call', + ], + operation: [ + 'make', + ], + }, + }, + }, + { + displayName: 'Answer URL', + name: 'answer_url', + type: 'string', + default: '', + description: 'URL to be invoked by Plivo once the call is answered.
It should return the XML to handle the call once answered.', + required: true, + displayOptions: { + show: { + resource: [ + 'call', + ], + operation: [ + 'make', + ], + }, + }, + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Plivo/GenericFunctions.ts b/packages/nodes-base/nodes/Plivo/GenericFunctions.ts new file mode 100644 index 0000000000..6af3a48acd --- /dev/null +++ b/packages/nodes-base/nodes/Plivo/GenericFunctions.ts @@ -0,0 +1,62 @@ +import { + IExecuteFunctions, + IHookFunctions, +} from 'n8n-core'; + +import { + IDataObject, +} from 'n8n-workflow'; + +/** + * Make an API request to Plivo. + * + * @param {IHookFunctions} this + * @param {string} method + * @param {string} url + * @param {object} body + * @returns {Promise} + */ +export async function plivoApiRequest( + this: IHookFunctions | IExecuteFunctions, + method: string, + endpoint: string, + body: IDataObject = {}, + qs: IDataObject = {}, +) { + + const credentials = this.getCredentials('plivoApi') as { authId: string, authToken: string }; + + if (!credentials) { + throw new Error('No credentials returned!'); + } + + const options = { + method, + form: body, + qs, + uri: `https://api.plivo.com/v1/Account/${credentials.authId}${endpoint}/`, + auth: { + user: credentials.authId, + pass: credentials.authToken, + }, + json: true, + }; + + try { + return await this.helpers.request(options); + } catch (error) { + if (error.statusCode === 401) { + throw new Error('Invalid Plivo credentials'); + } + if (error?.response?.body?.error) { + let errorMessage = `Plivo error response [${error.statusCode}]: ${error.response.body.error}`; + if (error.response.body.more_info) { + errorMessage = `errorMessage (${error.response.body.more_info})`; + } + + throw new Error(errorMessage); + } + + throw error; + } +} diff --git a/packages/nodes-base/nodes/Plivo/MmsDescription.ts b/packages/nodes-base/nodes/Plivo/MmsDescription.ts new file mode 100644 index 0000000000..398ae5f182 --- /dev/null +++ b/packages/nodes-base/nodes/Plivo/MmsDescription.ts @@ -0,0 +1,107 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +export const mmsOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'mms', + ], + }, + }, + options: [ + { + name: 'Send', + value: 'send', + description: 'Send an MMS message (US/Canada only)', + }, + ], + default: 'send', + description: 'Operation to perform.', + }, +] as INodeProperties[]; + +export const mmsFields = [ + // ---------------------------------- + // mms: send + // ---------------------------------- + { + displayName: 'From', + name: 'from', + type: 'string', + default: '', + description: 'Plivo Number to send the MMS from.', + placeholder: '+14156667777', + required: true, + displayOptions: { + show: { + resource: [ + 'mms', + ], + operation: [ + 'send', + ], + }, + }, + }, + { + displayName: 'To', + name: 'to', + type: 'string', + default: '', + description: 'Phone number to send the MMS to.', + placeholder: '+14156667778', + required: true, + displayOptions: { + show: { + operation: [ + 'send', + ], + resource: [ + 'mms', + ], + }, + }, + }, + { + displayName: 'Message', + name: 'message', + type: 'string', + default: '', + description: 'Message to send.', + required: false, + displayOptions: { + show: { + resource: [ + 'mms', + ], + operation: [ + 'send', + ], + }, + }, + }, + { + displayName: 'Media URLs', + name: 'media_urls', + type: 'string', + default: '', + required: false, + displayOptions: { + show: { + resource: [ + 'mms', + ], + operation: [ + 'send', + ], + }, + }, + description: 'Comma-separated list of media URLs of the files from your file server.', + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Plivo/Plivo.node.json b/packages/nodes-base/nodes/Plivo/Plivo.node.json new file mode 100644 index 0000000000..64e49874aa --- /dev/null +++ b/packages/nodes-base/nodes/Plivo/Plivo.node.json @@ -0,0 +1,21 @@ +{ + "node": "n8n-nodes-base.plivo", + "nodeVersion": "1.0", + "codexVersion": "1.0", + "categories": [ + "Communication", + "Development" + ], + "resources": { + "credentialDocumentation": [ + { + "url": "https://docs.n8n.io/credentials/plivo" + } + ], + "primaryDocumentation": [ + { + "url": "https://docs.n8n.io/nodes/n8n-nodes-base.plivo/" + } + ] + } +} \ No newline at end of file diff --git a/packages/nodes-base/nodes/Plivo/Plivo.node.ts b/packages/nodes-base/nodes/Plivo/Plivo.node.ts new file mode 100644 index 0000000000..3436c601f3 --- /dev/null +++ b/packages/nodes-base/nodes/Plivo/Plivo.node.ts @@ -0,0 +1,178 @@ +import { + IExecuteFunctions, +} from 'n8n-core'; + +import { + IDataObject, + INodeExecutionData, + INodeType, + INodeTypeDescription, +} from 'n8n-workflow'; + +import { + smsFields, + smsOperations, +} from './SmsDescription'; + +import { + mmsFields, + mmsOperations, +} from './MmsDescription'; + +import { + callFields, + callOperations, +} from './CallDescription'; + +import { + plivoApiRequest, +} from './GenericFunctions'; + +export class Plivo implements INodeType { + description: INodeTypeDescription = { + displayName: 'Plivo', + name: 'plivo', + icon: 'file:plivo.svg', + group: ['transform'], + version: 1, + subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', + description: 'Send SMS/MMS messages or make phone calls', + defaults: { + name: 'plivo', + color: '#43A046', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'plivoApi', + required: true, + }, + ], + properties: [ + { + displayName: 'Resource', + name: 'resource', + type: 'options', + options: [ + { + name: 'Call', + value: 'call', + }, + { + name: 'MMS', + value: 'mms', + }, + { + name: 'SMS', + value: 'sms', + }, + ], + default: 'sms', + required: true, + description: 'The resource to operate on.', + }, + ...smsOperations, + ...smsFields, + ...mmsOperations, + ...mmsFields, + ...callOperations, + ...callFields, + ], + }; + + async execute(this: IExecuteFunctions): Promise { + const items = this.getInputData(); + const returnData: IDataObject[] = []; + + const resource = this.getNodeParameter('resource', 0) as string; + const operation = this.getNodeParameter('operation', 0) as string; + + for (let i = 0; i < items.length; i++) { + + let responseData; + + if (resource === 'sms') { + + // ********************************************************************* + // sms + // ********************************************************************* + + if (operation === 'send') { + + // ---------------------------------- + // sms: send + // ---------------------------------- + + const body = { + src: this.getNodeParameter('from', i) as string, + dst: this.getNodeParameter('to', i) as string, + text: this.getNodeParameter('message', i) as string, + } as IDataObject; + + responseData = await plivoApiRequest.call(this, 'POST', '/Message', body); + + } + + } else if (resource === 'call') { + + // ********************************************************************* + // call + // ********************************************************************* + + if (operation === 'make') { + + // ---------------------------------- + // call: make + // ---------------------------------- + + // https://www.plivo.com/docs/voice/api/call#make-a-call + + const body = { + from: this.getNodeParameter('from', i) as string, + to: this.getNodeParameter('to', i) as string, + answer_url: this.getNodeParameter('answer_url', i) as string, + answer_method: this.getNodeParameter('answer_method', i) as string, + } as IDataObject; + + responseData = await plivoApiRequest.call(this, 'POST', '/Call', body); + + } + + } else if (resource === 'mms') { + + // ********************************************************************* + // mms + // ********************************************************************* + + if (operation === 'send') { + + // ---------------------------------- + // mss: send + // ---------------------------------- + + // https://www.plivo.com/docs/sms/api/message#send-a-message + + const body = { + src: this.getNodeParameter('from', i) as string, + dst: this.getNodeParameter('to', i) as string, + text: this.getNodeParameter('message', i) as string, + type: 'mms', + media_urls: this.getNodeParameter('media_urls', i) as string, + } as IDataObject; + + responseData = await plivoApiRequest.call(this, 'POST', '/Message', body); + + } + + } + + Array.isArray(responseData) + ? returnData.push(...responseData) + : returnData.push(responseData); + + } + + return [this.helpers.returnJsonArray(returnData)]; + } +} diff --git a/packages/nodes-base/nodes/Plivo/SmsDescription.ts b/packages/nodes-base/nodes/Plivo/SmsDescription.ts new file mode 100644 index 0000000000..ef1774fb93 --- /dev/null +++ b/packages/nodes-base/nodes/Plivo/SmsDescription.ts @@ -0,0 +1,89 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +export const smsOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'sms', + ], + }, + }, + options: [ + { + name: 'Send', + value: 'send', + description: 'Send an SMS message.', + }, + ], + default: 'send', + description: 'Operation to perform.', + }, +] as INodeProperties[]; + +export const smsFields = [ + // ---------------------------------- + // sms: send + // ---------------------------------- + { + displayName: 'From', + name: 'from', + type: 'string', + default: '', + description: 'Plivo Number to send the SMS from.', + placeholder: '+14156667777', + required: true, + displayOptions: { + show: { + resource: [ + 'sms', + ], + operation: [ + 'send', + ], + }, + }, + }, + { + displayName: 'To', + name: 'to', + type: 'string', + default: '', + description: 'Phone number to send the message to.', + placeholder: '+14156667778', + required: true, + displayOptions: { + show: { + resource: [ + 'sms', + ], + operation: [ + 'send', + ], + }, + }, + }, + { + displayName: 'Message', + name: 'message', + type: 'string', + default: '', + description: 'Message to send.', + required: true, + displayOptions: { + show: { + operation: [ + 'send', + ], + resource: [ + 'sms', + ], + }, + }, + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Plivo/plivo.svg b/packages/nodes-base/nodes/Plivo/plivo.svg new file mode 100644 index 0000000000..445ddb2563 --- /dev/null +++ b/packages/nodes-base/nodes/Plivo/plivo.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index cafa140270..b4317f1f93 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -178,6 +178,7 @@ "dist/credentials/PipedriveApi.credentials.js", "dist/credentials/PipedriveOAuth2Api.credentials.js", "dist/credentials/PhilipsHueOAuth2Api.credentials.js", + "dist/credentials/PlivoApi.credentials.js", "dist/credentials/Postgres.credentials.js", "dist/credentials/PostHogApi.credentials.js", "dist/credentials/PostmarkApi.credentials.js", @@ -444,6 +445,7 @@ "dist/nodes/Pipedrive/Pipedrive.node.js", "dist/nodes/Pipedrive/PipedriveTrigger.node.js", "dist/nodes/PhilipsHue/PhilipsHue.node.js", + "dist/nodes/Plivo/Plivo.node.js", "dist/nodes/Postgres/Postgres.node.js", "dist/nodes/PostHog/PostHog.node.js", "dist/nodes/Postmark/PostmarkTrigger.node.js",