feat: Add Twilio Trigger Node (#8859)

Co-authored-by: Michael Kret <michael.k@radency.com>
Co-authored-by: Giulio Andreini <andreini@netseven.it>
This commit is contained in:
Bram Kn 2024-03-29 09:24:25 +01:00 committed by GitHub
parent 2aab78b058
commit c204995d9c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 236 additions and 0 deletions

View file

@ -4,6 +4,8 @@ import type {
IDataObject,
IHttpRequestMethods,
IRequestOptions,
IHttpRequestOptions,
ILoadOptionsFunctions,
} from 'n8n-workflow';
/**
@ -40,6 +42,24 @@ export async function twilioApiRequest(
return await this.helpers.requestWithAuthentication.call(this, 'twilioApi', options);
}
export async function twilioTriggerApiRequest(
this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions,
method: IHttpRequestMethods,
endpoint: string,
body: FormData | IDataObject = {},
): Promise<any> {
const options: IHttpRequestOptions = {
method,
body,
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
url: `https://events.twilio.com/v1/${endpoint}`,
json: true,
};
return await this.helpers.requestWithAuthentication.call(this, 'twilioApi', options);
}
const XML_CHAR_MAP: { [key: string]: string } = {
'<': '&lt;',
'>': '&gt;',

View file

@ -0,0 +1,19 @@
{
"node": "n8n-nodes-base.twilioTrigger",
"nodeVersion": "1.0",
"codexVersion": "1.0",
"categories": ["Communication", "Development"],
"resources": {
"credentialDocumentation": [
{
"url": "https://docs.n8n.io/credentials/twilio"
}
],
"primaryDocumentation": [
{
"url": "https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.twilio-trigger/"
}
]
},
"alias": ["SMS", "Phone", "Voice"]
}

View file

@ -0,0 +1,196 @@
import type {
IHookFunctions,
IWebhookFunctions,
INodeType,
INodeTypeDescription,
IWebhookResponseData,
} from 'n8n-workflow';
import { twilioTriggerApiRequest } from './GenericFunctions';
export class TwilioTrigger implements INodeType {
description: INodeTypeDescription = {
displayName: 'Twilio Trigger',
name: 'twilioTrigger',
icon: 'file:twilio.svg',
group: ['trigger'],
version: [1],
defaultVersion: 1,
subtitle: '=Updates: {{$parameter["updates"].join(", ")}}',
description: 'Starts the workflow on a Twilio update',
defaults: {
name: 'Twilio Trigger',
},
inputs: [],
outputs: ['main'],
credentials: [
{
name: 'twilioApi',
required: true,
},
],
webhooks: [
{
name: 'default',
httpMethod: 'POST',
responseMode: 'onReceived',
path: 'webhook',
},
],
properties: [
{
displayName: 'Trigger On',
name: 'updates',
type: 'multiOptions',
options: [
{
name: 'New SMS',
value: 'com.twilio.messaging.inbound-message.received',
description: 'When an SMS message is received',
},
{
name: 'New Call',
value: 'com.twilio.voice.insights.call-summary.complete',
description: 'When a call is received',
},
],
required: true,
default: [],
},
{
displayName: "The 'New Call' event may take up to thirty minutes to be triggered",
name: 'callTriggerNotice',
type: 'notice',
default: '',
displayOptions: {
show: {
updates: ['com.twilio.voice.insights.call-summary.complete'],
},
},
},
],
};
webhookMethods = {
default: {
async checkExists(this: IHookFunctions): Promise<boolean> {
const webhookUrl = this.getNodeWebhookUrl('default');
const { sinks } = (await twilioTriggerApiRequest.call(this, 'GET', 'Sinks')) || {};
const sink = sinks.find(
(entry: { sink_configuration: { destination: string | undefined } }) =>
entry.sink_configuration.destination === webhookUrl,
);
if (sink) {
const { subscriptions } =
(await twilioTriggerApiRequest.call(this, 'GET', 'Subscriptions')) || {};
const subscription = subscriptions.find(
(entry: { sink_sid: any }) => entry.sink_sid === sink.sid,
);
if (subscription) {
const { types } =
(await twilioTriggerApiRequest.call(
this,
'GET',
`Subscriptions/${subscription.sid}/SubscribedEvents`,
)) || {};
const typesFound = types.map((type: { type: any }) => type.type);
const allowedUpdates = this.getNodeParameter('updates') as string[];
if (typesFound.sort().join(',') === allowedUpdates.sort().join(',')) {
return true;
} else {
return false;
}
}
}
return false;
},
async create(this: IHookFunctions): Promise<boolean> {
const workflowData = this.getWorkflowStaticData('node');
const webhookUrl = this.getNodeWebhookUrl('default');
const allowedUpdates = this.getNodeParameter('updates') as string[];
const bodySink = {
Description: 'Sink created by n8n Twilio Trigger Node.',
SinkConfiguration: `{ "destination": "${webhookUrl}", "method": "POST" }`,
SinkType: 'webhook',
};
const sink = await twilioTriggerApiRequest.call(this, 'POST', 'Sinks', bodySink);
workflowData.sinkId = sink.sid;
const body = {
Description: 'Subscription created by n8n Twilio Trigger Node.',
Types: `{ "type": "${allowedUpdates[0]}" }`,
SinkSid: sink.sid,
};
const subscription = await twilioTriggerApiRequest.call(
this,
'POST',
'Subscriptions',
body,
);
workflowData.subscriptionId = subscription.sid;
// if there is more than one event type add the others on the existing subscription
if (allowedUpdates.length > 1) {
for (let index = 1; index < allowedUpdates.length; index++) {
await twilioTriggerApiRequest.call(
this,
'POST',
`Subscriptions/${workflowData.subscriptionId}/SubscribedEvents`,
{
Type: allowedUpdates[index],
},
);
}
}
return true;
},
async delete(this: IHookFunctions): Promise<boolean> {
const workflowData = this.getWorkflowStaticData('node');
const sinkId = workflowData.sinkId;
const subscriptionId = workflowData.subscriptionId;
try {
if (sinkId) {
await twilioTriggerApiRequest.call(this, 'DELETE', `Sinks/${sinkId}`, {});
workflowData.sinkId = '';
}
if (subscriptionId) {
await twilioTriggerApiRequest.call(
this,
'DELETE',
`Subscriptions/${subscriptionId}`,
{},
);
workflowData.subscriptionId = '';
}
} catch (error) {
return false;
}
return true;
},
},
};
async webhook(this: IWebhookFunctions): Promise<IWebhookResponseData> {
const bodyData = this.getBodyData();
return {
workflowData: [this.helpers.returnJsonArray(bodyData)],
};
}
}

View file

@ -755,6 +755,7 @@
"dist/nodes/Trello/TrelloTrigger.node.js",
"dist/nodes/Twake/Twake.node.js",
"dist/nodes/Twilio/Twilio.node.js",
"dist/nodes/Twilio/TwilioTrigger.node.js",
"dist/nodes/Twist/Twist.node.js",
"dist/nodes/Twitter/Twitter.node.js",
"dist/nodes/Typeform/TypeformTrigger.node.js",