diff --git a/packages/nodes-base/nodes/Twilio/GenericFunctions.ts b/packages/nodes-base/nodes/Twilio/GenericFunctions.ts index a2d9cab967..a29dcd2fb8 100644 --- a/packages/nodes-base/nodes/Twilio/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Twilio/GenericFunctions.ts @@ -59,3 +59,17 @@ export async function twilioApiRequest(this: IHookFunctions | IExecuteFunctions, throw new NodeApiError(this.getNode(), error); } } + +const XML_CHAR_MAP: { [key: string]: string } = { + '<': '<', + '>': '>', + '&': '&', + '"': '"', + "'": ''' +}; + +export function escapeXml(str: string) { + return str.replace(/[<>&"']/g, function (ch: string) { + return XML_CHAR_MAP[ch]; + }); +} diff --git a/packages/nodes-base/nodes/Twilio/Twilio.node.json b/packages/nodes-base/nodes/Twilio/Twilio.node.json index 9dfbe17374..9874070754 100644 --- a/packages/nodes-base/nodes/Twilio/Twilio.node.json +++ b/packages/nodes-base/nodes/Twilio/Twilio.node.json @@ -61,6 +61,8 @@ ] }, "alias": [ - "SMS" + "SMS", + "Phone", + "Voice" ] -} \ No newline at end of file +} diff --git a/packages/nodes-base/nodes/Twilio/Twilio.node.ts b/packages/nodes-base/nodes/Twilio/Twilio.node.ts index 1aff7cc067..5d0ad13854 100644 --- a/packages/nodes-base/nodes/Twilio/Twilio.node.ts +++ b/packages/nodes-base/nodes/Twilio/Twilio.node.ts @@ -11,6 +11,7 @@ import { import { twilioApiRequest, + escapeXml, } from './GenericFunctions'; export class Twilio implements INodeType { @@ -44,6 +45,11 @@ export class Twilio implements INodeType { name: 'SMS', value: 'sms', }, + { + // eslint-disable-next-line n8n-nodes-base/node-param-resource-with-plural-option + name: 'Call', + value: 'call', + }, ], default: 'sms', }, @@ -70,13 +76,34 @@ export class Twilio implements INodeType { default: 'send', }, + { + displayName: 'Operation', + name: 'operation', + type: 'options', + noDataExpression: true, + displayOptions: { + show: { + resource: [ + 'call', + ], + }, + }, + options: [ + { + name: 'Make', + value: 'make', + }, + ], + default: 'make', + }, + // ---------------------------------- - // sms + // sms / call // ---------------------------------- // ---------------------------------- - // sms:send + // sms:send / call:make // ---------------------------------- { displayName: 'From', @@ -89,9 +116,11 @@ export class Twilio implements INodeType { show: { operation: [ 'send', + 'make', ], resource: [ 'sms', + 'call', ], }, }, @@ -108,9 +137,11 @@ export class Twilio implements INodeType { show: { operation: [ 'send', + 'make', ], resource: [ 'sms', + 'call', ], }, }, @@ -151,6 +182,40 @@ export class Twilio implements INodeType { }, description: 'The message to send', }, + { + displayName: 'Use TwiML', + name: 'twiml', + type: 'boolean', + default: false, + displayOptions: { + show: { + operation: [ + 'make', + ], + resource: [ + 'call', + ], + }, + }, + description: 'Whether to use the Twilio Markup Language in the message', + }, + { + displayName: 'Message', + name: 'message', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'make', + ], + resource: [ + 'call', + ], + }, + }, + }, { displayName: 'Options', name: 'options', @@ -218,6 +283,30 @@ export class Twilio implements INodeType { } else { throw new NodeOperationError(this.getNode(), `The operation "${operation}" is not known!`); } + } else if (resource === 'call') { + if (operation === 'make') { + // ---------------------------------- + // call:make + // ---------------------------------- + + requestMethod = 'POST'; + endpoint = '/Calls.json'; + + const message = this.getNodeParameter('message', i) as string; + const useTwiml = this.getNodeParameter('twiml', i) as boolean; + body.From = this.getNodeParameter('from', i) as string; + body.To = this.getNodeParameter('to', i) as string; + + if (useTwiml) { + body.Twiml = message; + } else { + body.Twiml = `${escapeXml(message)}`; + } + + body.StatusCallback = this.getNodeParameter('options.statusCallback', i, '') as string; + } else { + throw new NodeOperationError(this.getNode(), `The operation "${operation}" is not known!`); + } } else { throw new NodeOperationError(this.getNode(), `The resource "${resource}" is not known!`); }