Add Activepieces integration for workflow automation

- Add Activepieces fork with SmoothSchedule custom piece
- Create integrations app with Activepieces service layer
- Add embed token endpoint for iframe integration
- Create Automations page with embedded workflow builder
- Add sidebar visibility fix for embed mode
- Add list inactive customers endpoint to Public API
- Include SmoothSchedule triggers: event created/updated/cancelled
- Include SmoothSchedule actions: create/update/cancel events, list resources/services/customers

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
poduck
2025-12-18 22:59:37 -05:00
parent 9848268d34
commit 3aa7199503
16292 changed files with 1284892 additions and 4708 deletions

View File

@@ -0,0 +1,72 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { HttpMethod, httpClient, AuthenticationType, QueryParams } from '@activepieces/pieces-common';
import { twilioAuth } from '../..';
export const twilioDownloadRecordingMedia = createAction({
auth: twilioAuth,
name: 'download_recording_media',
displayName: 'Download Recording Media',
description: 'Download the media file for a specific recording.',
props: {
recording_sid: Property.ShortText({
displayName: 'Recording SID',
description: 'The unique identifier (SID) of the recording to download. It starts with "RE".',
required: true,
}),
format: Property.StaticDropdown({
displayName: 'Format',
description: 'The desired audio format for the download file.',
required: false,
defaultValue: 'mp3',
options: {
options: [
{ label: 'MP3', value: 'mp3' },
{ label: 'WAV', value: 'wav' },
],
},
}),
channels: Property.StaticDropdown({
displayName: 'Channels',
description: 'Specify whether to download a mono or dual-channel file. Note: Dual-channel may not be available for all recordings.',
required: false,
options: {
options: [
{ label: 'Mono', value: 1 },
{ label: 'Dual', value: 2 },
]
}
})
},
async run(context) {
const { recording_sid, format, channels } = context.propsValue;
const account_sid = context.auth.username;
const auth_token = context.auth.password;
const fileFormat = format ?? 'mp3';
const path = `Recordings/${recording_sid}.${fileFormat}`;
const queryParams: QueryParams = {};
if (channels) {
queryParams['RequestedChannels'] = channels.toString();
}
const response = await httpClient.sendRequest<ArrayBuffer>({
method: HttpMethod.GET,
url: `https://api.twilio.com/2010-04-01/Accounts/${account_sid}/${path}`,
queryParams: queryParams,
authentication: {
type: AuthenticationType.BASIC,
username: account_sid,
password: auth_token,
},
responseType: 'arraybuffer',
});
const fileData = Buffer.from(response.body);
return await context.files.write({
fileName: `${recording_sid}.${fileFormat}`,
data: fileData,
});
},
});

View File

@@ -0,0 +1,33 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { HttpMethod } from '@activepieces/pieces-common';
import { callTwilioApi } from '../common';
import { twilioAuth } from '../..';
export const twilioGetMessage = createAction({
auth: twilioAuth,
name: 'get_message',
description: 'Retrieves the details of a specific message.',
displayName: 'Get Message',
props: {
message_sid: Property.ShortText({
displayName: 'Message SID',
description: 'The unique identifier (SID) of the message to retrieve. It starts with "SM" or "MM".',
required: true,
}),
},
async run(context) {
const { message_sid } = context.propsValue;
const account_sid = context.auth.username;
const auth_token = context.auth.password;
const path = `Messages/${message_sid}.json`;
const response = await callTwilioApi(
HttpMethod.GET,
path,
{ account_sid, auth_token }
);
return response.body;
},
});

View File

@@ -0,0 +1,99 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { HttpMethod } from '@activepieces/pieces-common';
import { callTwilioApi, twilioCommon } from '../common';
import { twilioAuth } from '../..';
export const twilioMakeCall = createAction({
auth: twilioAuth,
name: 'make_call',
description: 'Call a number and say a message.',
displayName: 'Call Phone',
props: {
from: twilioCommon.phone_number,
to: Property.ShortText({
displayName: 'To',
description: 'The phone number to call. Must be in E.164 format (e.g., +15558675310).',
required: true,
}),
message: Property.LongText({
displayName: 'Message to Say',
description: 'The text message to be converted to speech and spoken to the recipient.',
required: true,
}),
voice: Property.StaticDropdown({
displayName: 'Voice',
description: 'The voice to use for the text-to-speech message.',
required: false,
options: {
options: [
{ label: 'Alice (Default)', value: 'alice' },
{ label: 'Man', value: 'man' },
{ label: 'Woman', value: 'woman' },
]
}
}),
language: Property.StaticDropdown({
displayName: 'Language',
description: 'The language to use for the text-to-speech message.',
required: false,
options: {
options: [
{ label: 'English (US)', value: 'en-US' },
{ label: 'English (UK)', value: 'en-GB' },
{ label: 'Spanish', value: 'es-ES' },
{ label: 'French', value: 'fr-FR' },
{ label: 'German', value: 'de-DE' },
]
}
}),
sendDigits: Property.ShortText({
displayName: 'Send DTMF Tones',
description: "A string of keys to dial after the call is connected. Use 'w' for a half-second pause.",
required: false,
}),
timeout: Property.Number({
displayName: 'Timeout (seconds)',
description: 'The number of seconds to let the phone ring before assuming no answer. Default is 60.',
required: false,
})
},
async run(context) {
const { from, to, message, voice, language, sendDigits, timeout } = context.propsValue;
const account_sid = context.auth.username;
const auth_token = context.auth.password;
// Construct TwiML for the <Say> verb
const voiceAttr = voice ? ` voice="${voice}"` : '';
const langAttr = language ? ` language="${language}"` : '';
const escapedMessage = message
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&apos;');
const twiml = `<Response><Say${voiceAttr}${langAttr}>${escapedMessage}</Say></Response>`;
const bodyParams: Record<string, unknown> = {
From: from,
To: to,
Twiml: twiml,
};
if (sendDigits) {
bodyParams['SendDigits'] = sendDigits;
}
if (timeout) {
bodyParams['Timeout'] = timeout;
}
const response = await callTwilioApi(
HttpMethod.POST,
'Calls.json',
{ account_sid, auth_token },
bodyParams
);
return response.body;
},
});

View File

@@ -0,0 +1,37 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { AuthenticationType, httpClient, HttpMethod } from '@activepieces/pieces-common';
import { twilioAuth } from '../..';
export const twilioPhoneNumberLookup = createAction({
auth: twilioAuth,
name: 'phone_number_lookup',
description: 'Lookup information about a phone number.',
displayName: 'Phone Number Lookup',
props: {
phone_number:Property.ShortText({
displayName:'Phone Number',
required:true
})
},
async run(context) {
const { phone_number } = context.propsValue;
const account_sid = context.auth.username;
const auth_token = context.auth.password;
const response = await httpClient.sendRequest({
method:HttpMethod.GET,
url:`https://lookups.twilio.com/v2/PhoneNumbers/${encodeURIComponent(phone_number)}`,
authentication: {
type: AuthenticationType.BASIC,
username: account_sid,
password: auth_token,
},
queryParams:{
Fields:'line_type_intelligence'
}
})
return response.body
},
});

View File

@@ -0,0 +1,39 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { HttpMethod } from '@activepieces/pieces-common';
import { callTwilioApi, twilioCommon } from '../common';
import { twilioAuth } from '../..';
export const twilioSendSms = createAction({
auth: twilioAuth,
name: 'send_sms',
description: 'Send a new SMS message',
displayName: 'Send SMS',
props: {
from: twilioCommon.phone_number,
body: Property.ShortText({
description: 'The body of the message to send',
displayName: 'Message Body',
required: true,
}),
to: Property.ShortText({
description: 'The phone number to send the message to',
displayName: 'To',
required: true,
}),
},
async run(context) {
const { body, to, from } = context.propsValue;
const account_sid = context.auth.username;
const auth_token = context.auth.password;
return await callTwilioApi(
HttpMethod.POST,
'Messages.json',
{ account_sid, auth_token },
{
From: from,
Body: body,
To: to,
}
);
},
});

View File

@@ -0,0 +1,69 @@
import {
Property,
BasicAuthPropertyValue,
} from '@activepieces/pieces-framework';
import {
HttpMethod,
HttpMessageBody,
httpClient,
AuthenticationType,
} from '@activepieces/pieces-common';
import { twilioAuth } from '../..';
export const twilioCommon = {
phone_number: Property.Dropdown({
auth: twilioAuth,
description: 'The phone number to send the message from',
displayName: 'From',
required: true,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'connect your account first',
options: [],
};
}
const basicAuthProperty = auth;
const response = await callTwilioApi<{
incoming_phone_numbers: {
phone_number: string;
friendly_name: string;
}[];
}>(HttpMethod.GET, 'IncomingPhoneNumbers.json', {
account_sid: basicAuthProperty.username,
auth_token: basicAuthProperty.password,
});
return {
disabled: false,
options: response.body.incoming_phone_numbers.map((number: any) => ({
value: number.phone_number,
label: number.friendly_name,
})),
};
},
}),
};
export const callTwilioApi = async <T extends HttpMessageBody>(
method: HttpMethod,
path: string,
auth: { account_sid: string; auth_token: string },
body?: any
) => {
return await httpClient.sendRequest<T>({
method,
url: `https://api.twilio.com/2010-04-01/Accounts/${auth.account_sid}/${path}`,
authentication: {
type: AuthenticationType.BASIC,
username: auth.account_sid,
password: auth.auth_token,
},
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: body,
});
};

View File

@@ -0,0 +1,153 @@
import { createTrigger, TriggerStrategy, PiecePropValueSchema, AppConnectionValueForAuthProperty } from '@activepieces/pieces-framework';
import { twilioAuth } from '../..';
import { AuthenticationType, DedupeStrategy, httpClient, HttpMethod, Polling, pollingHelper } from '@activepieces/pieces-common';
interface Call {
sid: string;
status: string;
direction: string;
from: string;
to: string;
start_time: string;
end_time: string;
duration: string;
price: string;
price_unit: string;
date_created:string
}
interface CallsResponse {
calls: Call[];
next_page_uri:string
}
const polling: Polling<
AppConnectionValueForAuthProperty<typeof twilioAuth>,
Record<string, unknown>
> = {
strategy: DedupeStrategy.TIMEBASED,
async items({ auth, lastFetchEpochMS }) {
const isTest = lastFetchEpochMS === 0;
const account_sid = auth.username;
const auth_token = auth.password;
let currentUri:
| string
| null = `/2010-04-01/Accounts/${account_sid}/Calls.json?PageSize=${
isTest ? 1 : 1000
}`;
const results = [];
let stop = false;
do {
const response: any = await httpClient.sendRequest({
method: HttpMethod.GET,
url: `https://api.twilio.com${currentUri}`,
authentication: {
type: AuthenticationType.BASIC,
username: account_sid,
password: auth_token,
},
});
const payload = response.body as CallsResponse;
const calls = payload.calls ?? [];
for (const call of calls) {
const ts = new Date(call.date_created).getTime();
if(call.status !== 'completed') continue;
if (isTest || ts > lastFetchEpochMS) {
results.push(call);
} else {
stop = true;
break;
}
}
if (isTest) break;
currentUri = payload?.next_page_uri ?? null;
} while (currentUri && !stop);
return results.map((call) => {
return {
epochMilliSeconds: new Date(call.date_created).getTime(),
data: call,
};
});
},
};
export const twilioNewCall = createTrigger({
auth: twilioAuth,
name: 'new_call',
displayName: 'New Call',
description: 'Triggers when a call completes (incoming or outgoing).',
props: {},
sampleData:{
"account_sid": "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"answered_by": "machine_start",
"api_version": "2010-04-01",
"caller_name": "callerid1",
"date_created": "Fri, 18 Oct 2019 17:00:00 +0000",
"date_updated": "Fri, 18 Oct 2019 17:01:00 +0000",
"direction": "outbound-api",
"duration": "4",
"end_time": "Fri, 18 Oct 2019 17:03:00 +0000",
"forwarded_from": "calledvia1",
"from": "+13051416799",
"from_formatted": "(305) 141-6799",
"group_sid": "GPdeadbeefdeadbeefdeadbeefdeadbeef",
"parent_call_sid": "CAdeadbeefdeadbeefdeadbeefdeadbeef",
"phone_number_sid": "PNdeadbeefdeadbeefdeadbeefdeadbeef",
"price": "-0.200",
"price_unit": "USD",
"sid": "CAaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"start_time": "Fri, 18 Oct 2019 17:02:00 +0000",
"status": "completed",
"subresource_uris": {
"notifications": "/2010-04-01/Accounts/ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Calls/CAaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Notifications.json",
"recordings": "/2010-04-01/Accounts/ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Calls/CAaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Recordings.json",
"payments": "/2010-04-01/Accounts/ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Calls/CAaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Payments.json",
"events": "/2010-04-01/Accounts/ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Calls/CAaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Events.json",
"siprec": "/2010-04-01/Accounts/ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Calls/CAaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Siprec.json",
"streams": "/2010-04-01/Accounts/ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Calls/CAaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Streams.json",
"transcriptions": "/2010-04-01/Accounts/ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Calls/CAaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Transcriptions.json",
"user_defined_message_subscriptions": "/2010-04-01/Accounts/ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Calls/CAaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/UserDefinedMessageSubscriptions.json",
"user_defined_messages": "/2010-04-01/Accounts/ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Calls/CAaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/UserDefinedMessages.json"
},
"to": "+13051913581",
"to_formatted": "(305) 191-3581",
"trunk_sid": "TKdeadbeefdeadbeefdeadbeefdeadbeef",
"uri": "/2010-04-01/Accounts/ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Calls/CAaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.json",
"queue_time": "1000"
},
type: TriggerStrategy.POLLING,
async onEnable(context) {
await pollingHelper.onEnable(polling, {
auth: context.auth,
store: context.store,
propsValue: context.propsValue,
});
},
async onDisable(context) {
await pollingHelper.onDisable(polling, {
auth: context.auth,
store: context.store,
propsValue: context.propsValue,
});
},
async test(context) {
return await pollingHelper.test(polling, context);
},
async run(context) {
return await pollingHelper.poll(polling, context);
},
});

View File

@@ -0,0 +1,120 @@
import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework';
import {
HttpMethod,
HttpResponse,
httpClient,
AuthenticationType,
} from '@activepieces/pieces-common';
import { callTwilioApi, twilioCommon } from '../common';
import { twilioAuth } from '../..';
export const twilioNewIncomingSms = createTrigger({
auth: twilioAuth,
name: 'new_incoming_sms',
displayName: 'New Incoming SMS',
description: 'Triggers when a new SMS message is received',
props: {
phone_number: twilioCommon.phone_number,
},
sampleData: {
body: 'Hello',
num_segments: '1',
direction: 'inbound',
from: '+12184191735',
date_updated: 'Wed, 08 Feb 2023 01:40:51 +0000',
price: null,
error_message: null,
uri: '/2010-04-01/Accounts/ACc0ea1238d61fe90d78a69a3de71d45619/Messages/SM8c3920d3f2ac481ba83e639a69dadd63.json',
account_sid: 'ACc0ea716d61fe90d78a123a3de71d45619',
num_media: '0',
to: '+12184191735',
date_created: 'Wed, 08 Feb 2023 01:40:50 +0000',
status: 'failed',
sid: 'SM8c3920d3f2ac481ba83e639a69dadd63',
date_sent: 'Wed, 08 Feb 2023 01:40:51 +0000',
messaging_service_sid: 'MG88e323e6a88ce67ba3bf12e1bcb7e0b8',
error_code: 21211,
price_unit: 'USD',
api_version: '2010-04-01',
subresource_uris: {
media:
'/2010-04-01/Accounts/ACc0ea716d61fe90d78a69a3de71d45619/Messages/SM8c3920d3f2ac481ba83e639a69dadd63/Media.json',
feedback:
'/2010-04-01/Accounts/ACc0ea716d61fe90d78a69a3de71d45619/Messages/SM8c3920d3f2ac481ba83e639a69dadd63/Feedback.json',
},
},
// Twilio API only allows one webhook per phone number, so we need to poll
type: TriggerStrategy.POLLING,
async onEnable(context) {
const { phone_number } = context.propsValue;
const account_sid = context.auth.username;
const auth_token = context.auth.password;
const response = await callTwilioApi<MessagePaginationResponse>(
HttpMethod.GET,
`Messages.json?PageSize=20&To=${phone_number}`,
{ account_sid, auth_token },
{}
);
await context.store.put<LastMessage>('_new_incoming_sms_trigger', {
lastMessageId:
response.body.messages.length === 0
? null
: response.body.messages[0].sid,
});
},
async onDisable(context) {
await context.store.put('_new_incoming_sms_trigger', null);
},
async run(context) {
const account_sid = context.auth.username;
const auth_token = context.auth.password;
const newMessages: unknown[] = [];
const lastMessage = await context.store.get<LastMessage>(
'_new_incoming_sms_trigger'
);
let currentUri:
| string
| null = `2010-04-01/Accounts/${account_sid}/Messages.json?PageSize=20&To=${context.propsValue.phone_number}`;
let firstMessageId = undefined;
while (currentUri !== undefined && currentUri !== null) {
const res: HttpResponse<MessagePaginationResponse> =
await httpClient.sendRequest<MessagePaginationResponse>({
method: HttpMethod.GET,
url: `https://api.twilio.com/${currentUri}`,
authentication: {
type: AuthenticationType.BASIC,
username: account_sid,
password: auth_token,
},
});
const messages = res.body.messages;
if (!firstMessageId && messages.length > 0) {
firstMessageId = messages[0].sid;
}
currentUri = res.body.next_page_uri;
for (let i = 0; i < messages.length; i++) {
const message = messages[i];
if (message.sid === lastMessage?.lastMessageId) {
currentUri = null;
break;
}
if (message.direction === 'inbound') {
newMessages.push(message);
}
}
}
await context.store.put<LastMessage>('_new_incoming_sms_trigger', {
lastMessageId: firstMessageId ?? lastMessage!.lastMessageId,
});
return newMessages;
},
});
interface LastMessage {
lastMessageId: string | null;
}
interface MessagePaginationResponse {
messages: { sid: string; to: string; status: string; direction: string }[];
next_page_uri: string;
}

View File

@@ -0,0 +1,146 @@
import {
AppConnectionValueForAuthProperty,
createTrigger,
PiecePropValueSchema,
TriggerStrategy,
} from '@activepieces/pieces-framework';
import {
AuthenticationType,
DedupeStrategy,
httpClient,
HttpMethod,
Polling,
pollingHelper,
} from '@activepieces/pieces-common';
import { twilioAuth } from '../..';
// The full structure of an IncomingPhoneNumber object from the Twilio API
interface TwilioPhoneNumber {
sid: string;
account_sid: string;
friendly_name: string;
phone_number: string;
voice_url: string;
voice_method: string;
sms_url: string;
sms_method: string;
date_created: string;
date_updated: string;
capabilities: {
voice: boolean;
sms: boolean;
mms: boolean;
};
uri: string;
}
interface IncomingPhoneNumbersResponse {
incoming_phone_numbers: TwilioPhoneNumber[];
next_page_uri:string
}
const polling: Polling<
AppConnectionValueForAuthProperty<typeof twilioAuth>,
Record<string, unknown>
> = {
strategy: DedupeStrategy.TIMEBASED,
async items({ auth, lastFetchEpochMS }) {
const isTest = lastFetchEpochMS === 0;
const account_sid = auth.username;
const auth_token = auth.password;
let currentUri:
| string
| null = `/2010-04-01/Accounts/${account_sid}/IncomingPhoneNumbers.json?PageSize=${
isTest ? 10 : 1000
}`;
const results = [];
let stop = false;
do {
const response: any = await httpClient.sendRequest({
method: HttpMethod.GET,
url: `https://api.twilio.com${currentUri}`,
authentication: {
type: AuthenticationType.BASIC,
username: account_sid,
password: auth_token,
},
});
const payload = response.body as IncomingPhoneNumbersResponse;
const numbers = payload.incoming_phone_numbers ?? [];
for (const num of numbers) {
const ts = new Date(num.date_created).getTime();
if (isTest || ts > lastFetchEpochMS) {
results.push(num);
} else {
stop = true;
break;
}
}
if (isTest) break;
currentUri = payload?.next_page_uri ?? null;
} while (currentUri && !stop);
return results.map((num) => {
return {
epochMilliSeconds: new Date(num.date_created).getTime(),
data: num,
};
});
},
};
export const twilioNewPhoneNumber = createTrigger({
auth: twilioAuth,
name: 'new_phone_number',
displayName: 'New Phone Number',
description: 'Triggers when you add a new phone number to your account.',
props: {},
sampleData: {
sid: 'PNXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
account_sid: 'ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
friendly_name: '(555) 123-4567',
phone_number: '+15551234567',
voice_url: 'https://demo.twilio.com/welcome/voice/',
voice_method: 'POST',
sms_url: 'https://demo.twilio.com/welcome/sms/',
sms_method: 'POST',
date_created: '2025-08-28T11:44:10+00:00',
date_updated: '2025-08-28T11:44:10+00:00',
capabilities: {
voice: true,
sms: true,
mms: true,
},
uri: '/2010-04-01/Accounts/ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/IncomingPhoneNumbers/PNXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.json',
},
type: TriggerStrategy.POLLING,
async onEnable(context) {
await pollingHelper.onEnable(polling, {
auth: context.auth,
store: context.store,
propsValue: context.propsValue,
});
},
async onDisable(context) {
await pollingHelper.onDisable(polling, {
auth: context.auth,
store: context.store,
propsValue: context.propsValue,
});
},
async test(context) {
return await pollingHelper.test(polling, context);
},
async run(context) {
return await pollingHelper.poll(polling, context);
},
});

View File

@@ -0,0 +1,137 @@
import { AppConnectionValueForAuthProperty, createTrigger, PiecePropValueSchema, TriggerStrategy } from '@activepieces/pieces-framework';
import { AuthenticationType, DedupeStrategy, httpClient, HttpMethod, Polling, pollingHelper } from '@activepieces/pieces-common';
import { twilioAuth } from '../..';
interface Recording {
sid: string;
status: string;
date_created: string;
call_sid: string;
duration: string;
source: string;
price: string;
price_unit: string;
}
interface RecordingsResponse {
recordings: Recording[];
next_page_uri:string
}
const polling: Polling<
AppConnectionValueForAuthProperty<typeof twilioAuth>,
Record<string, unknown>
> = {
strategy: DedupeStrategy.TIMEBASED,
async items({ auth, lastFetchEpochMS }) {
const isTest = lastFetchEpochMS === 0;
const account_sid = auth.username;
const auth_token = auth.password;
let currentUri:
| string
| null = `/2010-04-01/Accounts/${account_sid}/Recordings.json?PageSize=${
isTest ? 10 : 1000
}`;
const results = [];
let stop = false;
do {
const response: any = await httpClient.sendRequest({
method: HttpMethod.GET,
url: `https://api.twilio.com${currentUri}`,
authentication: {
type: AuthenticationType.BASIC,
username: account_sid,
password: auth_token,
},
});
const payload = response.body as RecordingsResponse;
const recordings = payload.recordings ?? [];
for (const recording of recordings) {
const ts = new Date(recording.date_created).getTime();
if (isTest || ts > lastFetchEpochMS) {
results.push(recording);
} else {
stop = true;
break;
}
}
if (isTest) break;
currentUri = payload?.next_page_uri ?? null;
} while (currentUri && !stop);
return results.map((recording) => {
return {
epochMilliSeconds: new Date(recording.date_created).getTime(),
data: recording,
};
});
},
};
export const twilioNewRecording = createTrigger({
auth: twilioAuth,
name: 'new_recording',
displayName: 'New Recording',
description: 'Triggers when a new call recording is completed and available.',
props: {},
sampleData: {
"account_sid": "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"api_version": "2010-04-01",
"call_sid": "CAaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"conference_sid": "CFaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"channels": 1,
"date_created": "Fri, 14 Oct 2016 21:56:34 +0000",
"date_updated": "Fri, 14 Oct 2016 21:56:38 +0000",
"start_time": "Fri, 14 Oct 2016 21:56:34 +0000",
"price": "0.04",
"price_unit": "USD",
"duration": "4",
"sid": "REaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"source": "StartConferenceRecordingAPI",
"status": "completed",
"error_code": null,
"uri": "/2010-04-01/Accounts/ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Recordings/REaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.json",
"subresource_uris": {
"add_on_results": "/2010-04-01/Accounts/ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Recordings/REaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/AddOnResults.json",
"transcriptions": "/2010-04-01/Accounts/ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Recordings/REaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Transcriptions.json"
},
"encryption_details": {
"encryption_public_key_sid": "CRaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"encryption_cek": "OV4h6zrsxMIW7h0Zfqwfn6TI2GCNl54KALlg8wn8YB8KYZhXt6HlgvBWAmQTlfYVeLWydMiCewY0YkDDT1xmNe5huEo9vjuKBS5OmYK4CZkSx1NVv3XOGrZHpd2Pl/5WJHVhUK//AUO87uh5qnUP2E0KoLh1nyCLeGcEkXU0RfpPn/6nxjof/n6m6OzZOyeIRK4Oed5+rEtjqFDfqT0EVKjs6JAxv+f0DCc1xYRHl2yV8bahUPVKs+bHYdy4PVszFKa76M/Uae4jFA9Lv233JqWcxj+K2UoghuGhAFbV/JQIIswY2CBYI8JlVSifSqNEl9vvsTJ8bkVMm3MKbG2P7Q==",
"encryption_iv": "8I2hhNIYNTrwxfHk"
},
"media_url": "http://api.twilio.com/2010-04-01/Accounts/ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Recordings/REaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
},
type: TriggerStrategy.POLLING,
async onEnable(context) {
await pollingHelper.onEnable(polling, {
auth: context.auth,
store: context.store,
propsValue: context.propsValue,
});
},
async onDisable(context) {
await pollingHelper.onDisable(polling, {
auth: context.auth,
store: context.store,
propsValue: context.propsValue,
});
},
async test(context) {
return await pollingHelper.test(polling, context);
},
async run(context) {
return await pollingHelper.poll(polling, context);
},
});

View File

@@ -0,0 +1,137 @@
import {
AppConnectionValueForAuthProperty,
createTrigger,
PiecePropValueSchema,
TriggerStrategy,
} from '@activepieces/pieces-framework';
import {
AuthenticationType,
DedupeStrategy,
httpClient,
HttpMethod,
Polling,
pollingHelper,
} from '@activepieces/pieces-common';
import { twilioAuth } from '../..';
interface Transcription {
sid: string;
status: string;
date_created: string;
recording_sid: string;
duration: string;
price: string;
price_unit: string;
transcription_text: string;
}
interface TranscriptionsResponse {
transcriptions: Transcription[];
next_page_uri: string;
}
const polling: Polling<
AppConnectionValueForAuthProperty<typeof twilioAuth>,
Record<string, unknown>
> = {
strategy: DedupeStrategy.TIMEBASED,
async items({ auth, lastFetchEpochMS }) {
const isTest = lastFetchEpochMS === 0;
const account_sid = auth.username;
const auth_token = auth.password;
let currentUri:
| string
| null = `/2010-04-01/Accounts/${account_sid}/Transcriptions.json?PageSize=${
isTest ? 10 : 1000
}`;
const results = [];
let stop = false;
do {
const response: any = await httpClient.sendRequest({
method: HttpMethod.GET,
url: `https://api.twilio.com${currentUri}`,
authentication: {
type: AuthenticationType.BASIC,
username: account_sid,
password: auth_token,
},
});
const payload = response.body as TranscriptionsResponse;
const transcriptions = payload.transcriptions ?? [];
for (const recording of transcriptions) {
const ts = new Date(recording.date_created).getTime();
if (recording.status !== 'completed') continue;
if (isTest || ts > lastFetchEpochMS) {
results.push(recording);
} else {
stop = true;
break;
}
}
if (isTest) break;
currentUri = payload?.next_page_uri ?? null;
} while (currentUri && !stop);
return results.map((recording) => {
return {
epochMilliSeconds: new Date(recording.date_created).getTime(),
data: recording,
};
});
},
};
export const twilioNewTranscription = createTrigger({
auth: twilioAuth,
name: 'new_transcription',
displayName: 'New Transcription',
description: 'Triggers when a new call recording transcription is completed.',
props: {},
sampleData: {
"account_sid": "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"api_version": "2008-08-01",
"date_created": "Thu, 25 Aug 2011 20:59:45 +0000",
"date_updated": "Thu, 25 Aug 2011 20:59:45 +0000",
"duration": "10",
"price": "0.00000",
"price_unit": "USD",
"recording_sid": "REaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"sid": "TRaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"status": "completed",
"transcription_text": null,
"type": "fast",
"uri": "/2010-04-01/Accounts/ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Transcriptions/TRaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.json"
},
type: TriggerStrategy.POLLING,
async onEnable(context) {
await pollingHelper.onEnable(polling, {
auth: context.auth,
store: context.store,
propsValue: context.propsValue,
});
},
async onDisable(context) {
await pollingHelper.onDisable(polling, {
auth: context.auth,
store: context.store,
propsValue: context.propsValue,
});
},
async test(context) {
return await pollingHelper.test(polling, context);
},
async run(context) {
return await pollingHelper.poll(polling, context);
},
});