diff --git a/packages/backend/src/apps/signalwire/actions/index.ts b/packages/backend/src/apps/signalwire/actions/index.ts new file mode 100644 index 00000000..d1723dc2 --- /dev/null +++ b/packages/backend/src/apps/signalwire/actions/index.ts @@ -0,0 +1,3 @@ +import sendSms from './send-sms'; + +export default [sendSms]; diff --git a/packages/backend/src/apps/signalwire/actions/send-sms/index.ts b/packages/backend/src/apps/signalwire/actions/send-sms/index.ts new file mode 100644 index 00000000..8f91daa2 --- /dev/null +++ b/packages/backend/src/apps/signalwire/actions/send-sms/index.ts @@ -0,0 +1,50 @@ +import defineAction from '../../../../helpers/define-action'; + +export default defineAction({ + name: 'Send an SMS', + key: 'sendSms', + description: 'Sends an SMS', + arguments: [ + { + label: 'From Number', + key: 'fromNumber', + type: 'string' as const, + required: true, + description: + 'The number to send the SMS from. Include only country code. Example: 491234567890', + variables: true, + }, + { + label: 'To Number', + key: 'toNumber', + type: 'string' as const, + required: true, + description: + 'The number to send the SMS to. Include only country code. Example: 491234567890', + variables: true, + }, + { + label: 'Message', + key: 'message', + type: 'string' as const, + required: true, + description: 'The content of the message.', + variables: true, + }, + ], + + async run($) { + const requestPath = `/api/laml/2010-04-01/Accounts/${$.auth.data.accountSid}/Messages`; + const messageBody = $.step.parameters.message; + + const fromNumber = '%2B' + ($.step.parameters.fromNumber as string).trim(); + const toNumber = '%2B' + ($.step.parameters.toNumber as string).trim(); + + const response = await $.http.post( + 'https://' + $.auth.data.spaceName + '.' + $.auth.data.spaceRegion + 'signalwire.com' + requestPath, + `Body=${messageBody}&From=${fromNumber}&To=${toNumber}` + ); + + $.setActionItem({ raw: response.data }); + }, +}); diff --git a/packages/backend/src/apps/signalwire/assets/favicon.svg b/packages/backend/src/apps/signalwire/assets/favicon.svg new file mode 100644 index 00000000..1dda2037 --- /dev/null +++ b/packages/backend/src/apps/signalwire/assets/favicon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/backend/src/apps/signalwire/auth/index.ts b/packages/backend/src/apps/signalwire/auth/index.ts new file mode 100644 index 00000000..009eca43 --- /dev/null +++ b/packages/backend/src/apps/signalwire/auth/index.ts @@ -0,0 +1,65 @@ +import verifyCredentials from './verify-credentials'; +import isStillVerified from './is-still-verified'; + +export default { + fields: [ + { + key: 'accountSid', + label: 'Project ID', + type: 'string' as const, + required: true, + readOnly: false, + value: null, + placeholder: null, + description: + 'Log into your Signalwire account and find the Project ID', + clickToCopy: false, + }, + { + key: 'authToken', + label: 'API Token', + type: 'string' as const, + required: true, + readOnly: false, + value: null, + placeholder: null, + description: 'API Token in the respective project', + clickToCopy: false, + }, + { + key: 'spaceRegion', + label: 'Signalwire Region', + type: 'dropdown' as const, + required: true, + readOnly: false, + value: '', + placeholder: null, + description: 'Most people should choose the default, "US"', + clickToCopy: false, + options: [ + { + label: 'US', + value: '', + }, + { + label: 'EU', + value: 'eu-', + }, + ], + }, + { + key: 'spaceName', + label: 'Space Name', + type: 'string' as const, + required: true, + readOnly: false, + value: null, + placeholder: null, + description: 'Name of your Signalwire space that contains the project', + clickToCopy: true, + }, + ], + + verifyCredentials, + isStillVerified, +}; diff --git a/packages/backend/src/apps/signalwire/auth/is-still-verified.ts b/packages/backend/src/apps/signalwire/auth/is-still-verified.ts new file mode 100644 index 00000000..66bb963e --- /dev/null +++ b/packages/backend/src/apps/signalwire/auth/is-still-verified.ts @@ -0,0 +1,9 @@ +import { IGlobalVariable } from '@automatisch/types'; +import verifyCredentials from './verify-credentials'; + +const isStillVerified = async ($: IGlobalVariable) => { + await verifyCredentials($); + return true; +}; + +export default isStillVerified; diff --git a/packages/backend/src/apps/signalwire/auth/verify-credentials.ts b/packages/backend/src/apps/signalwire/auth/verify-credentials.ts new file mode 100644 index 00000000..ef75de44 --- /dev/null +++ b/packages/backend/src/apps/signalwire/auth/verify-credentials.ts @@ -0,0 +1,11 @@ +import { IGlobalVariable } from '@automatisch/types'; + +const verifyCredentials = async ($: IGlobalVariable) => { + await $.http.get('https://' + $.auth.data.spaceName + '.' + $.auth.data.spaceRegion + 'signalwire.com' + '/api/laml/2010-04-01/Accounts'); + + await $.auth.set({ + screenName: $.auth.data.accountSid, + }); +}; + +export default verifyCredentials; diff --git a/packages/backend/src/apps/signalwire/common/add-auth-header.ts b/packages/backend/src/apps/signalwire/common/add-auth-header.ts new file mode 100644 index 00000000..f5f47f85 --- /dev/null +++ b/packages/backend/src/apps/signalwire/common/add-auth-header.ts @@ -0,0 +1,20 @@ +import { TBeforeRequest } from '@automatisch/types'; + +const addAuthHeader: TBeforeRequest = ($, requestConfig) => { + if ( + requestConfig.headers && + $.auth.data?.accountSid && + $.auth.data?.authToken + ) { + requestConfig.headers['Content-Type'] = 'application/x-www-form-urlencoded'; + + requestConfig.auth = { + username: $.auth.data.accountSid as string, + password: $.auth.data.authToken as string, + }; + } + + return requestConfig; +}; + +export default addAuthHeader; diff --git a/packages/backend/src/apps/signalwire/index.d.ts b/packages/backend/src/apps/signalwire/index.d.ts new file mode 100644 index 00000000..e69de29b diff --git a/packages/backend/src/apps/signalwire/index.ts b/packages/backend/src/apps/signalwire/index.ts new file mode 100644 index 00000000..5df3ed0f --- /dev/null +++ b/packages/backend/src/apps/signalwire/index.ts @@ -0,0 +1,20 @@ +import defineApp from '../../helpers/define-app'; +import addAuthHeader from './common/add-auth-header'; +import auth from './auth'; +import triggers from './triggers'; +import actions from './actions'; + +export default defineApp({ + name: 'Signalwire', + key: 'signalwire', + iconUrl: 'https://signalwire.com/favicon.svg', + authDocUrl: 'https://automatisch.io/docs/apps/signalwire/connection', + supportsConnections: true, + baseUrl: 'https://signalwire.com', + apiBaseUrl: '', + primaryColor: '044cf6', + beforeRequest: [addAuthHeader], + auth, + triggers, + actions, +}); diff --git a/packages/backend/src/apps/signalwire/triggers/index.ts b/packages/backend/src/apps/signalwire/triggers/index.ts new file mode 100644 index 00000000..04e1504d --- /dev/null +++ b/packages/backend/src/apps/signalwire/triggers/index.ts @@ -0,0 +1,3 @@ +import receiveSms from './receive-sms'; + +export default [receiveSms]; diff --git a/packages/backend/src/apps/signalwire/triggers/receive-sms/fetch-messages.ts b/packages/backend/src/apps/signalwire/triggers/receive-sms/fetch-messages.ts new file mode 100644 index 00000000..31963130 --- /dev/null +++ b/packages/backend/src/apps/signalwire/triggers/receive-sms/fetch-messages.ts @@ -0,0 +1,27 @@ +import { IGlobalVariable, IJSONObject } from '@automatisch/types'; + +const fetchMessages = async ($: IGlobalVariable) => { + const toNumber = $.step.parameters.toNumber as string; + + let response; + let requestPath = 'https://' + $.auth.data.spaceName + '.' + $.auth.data.spaceRegion + 'signalwire.com' + `/api/laml/2010-04-01/Accounts/${$.auth.data.accountSid}/Messages?To=${toNumber}`; + + do { + response = await $.http.get(requestPath); + + response.data.messages.forEach((message: IJSONObject) => { + const dataItem = { + raw: message, + meta: { + internalId: message.date_sent as string, + }, + }; + + $.pushTriggerItem(dataItem); + }); + + requestPath = 'https://' + $.auth.data.spaceName + '.' + $.auth.data.spaceRegion + 'signalwire.com' + response.data.next_page_uri; + } while (requestPath); +}; + +export default fetchMessages; diff --git a/packages/backend/src/apps/signalwire/triggers/receive-sms/index.ts b/packages/backend/src/apps/signalwire/triggers/receive-sms/index.ts new file mode 100644 index 00000000..6260d418 --- /dev/null +++ b/packages/backend/src/apps/signalwire/triggers/receive-sms/index.ts @@ -0,0 +1,23 @@ +import defineTrigger from '../../../../helpers/define-trigger'; +import fetchMessages from './fetch-messages'; + +export default defineTrigger({ + name: 'Receive SMS', + key: 'receiveSms', + pollInterval: 15, + description: 'Triggers when a new SMS is received.', + arguments: [ + { + label: 'To Number', + key: 'toNumber', + type: 'string', + required: true, + description: + 'The number to receive the SMS on. It should be a Signalwire number in your project.', + }, + ], + + async run($) { + await fetchMessages($); + }, +}); diff --git a/packages/docs/pages/.vitepress/config.js b/packages/docs/pages/.vitepress/config.js index 3a16f115..f6a0589f 100644 --- a/packages/docs/pages/.vitepress/config.js +++ b/packages/docs/pages/.vitepress/config.js @@ -133,6 +133,16 @@ export default defineConfig({ { text: 'Connection', link: '/apps/scheduler/connection' }, ], }, + { + text: 'Signalwire', + collapsible: true, + collapsed: true, + items: [ + { text: 'Triggers', link: '/apps/signalwire/triggers' }, + { text: 'Actions', link: '/apps/signalwire/actions' }, + { text: 'Connection', link: '/apps/signalwire/connection' }, + ], + }, { text: 'Slack', collapsible: true, diff --git a/packages/docs/pages/apps/signalwire/actions.md b/packages/docs/pages/apps/signalwire/actions.md new file mode 100644 index 00000000..bc89edd2 --- /dev/null +++ b/packages/docs/pages/apps/signalwire/actions.md @@ -0,0 +1,12 @@ +--- +favicon: /favicons/signalwire.svg +items: + - name: Send an SMS + desc: Sends an SMS +--- + + + + diff --git a/packages/docs/pages/apps/signalwire/connection.md b/packages/docs/pages/apps/signalwire/connection.md new file mode 100644 index 00000000..8100b06f --- /dev/null +++ b/packages/docs/pages/apps/signalwire/connection.md @@ -0,0 +1,16 @@ +# Signalwire + +:::info +This page explains the steps you need to follow to set up a Signalwire connection in Automatisch. If any of the steps are outdated, please let us know! +::: + +1. Go to the Signalwire API page in your respective project (https://{space}.signalwire.com/credentials) +2. Copy **Project ID** and paste it to the **Project ID** field on the + Automatisch connection creation page. +3. Create/Copy **API Token** and paste it to the **API Token** field on the + Automatisch connection creation page. +4. Select your **Region** (US for most users). +5. Provide your **Space Name** from the URL and paste it to the **Space NAME** field on the + Automatisch connection creation page. +6. Click **Submit** button on Automatisch. +7. Now you can start using the new Signalwire connection! diff --git a/packages/docs/pages/apps/signalwire/triggers.md b/packages/docs/pages/apps/signalwire/triggers.md new file mode 100644 index 00000000..42083cc8 --- /dev/null +++ b/packages/docs/pages/apps/signalwire/triggers.md @@ -0,0 +1,12 @@ +--- +favicon: /favicons/signalwire.svg +items: + - name: Receive SMS + desc: Triggers when a new SMS is received. +--- + + + + diff --git a/packages/docs/pages/build-integrations/examples.md b/packages/docs/pages/build-integrations/examples.md index 308be03f..66895181 100644 --- a/packages/docs/pages/build-integrations/examples.md +++ b/packages/docs/pages/build-integrations/examples.md @@ -33,6 +33,7 @@ The build integrations section is best understood when read from beginning to en - [DeepL](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/deepl/auth/index.ts) - [Twilio](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/twilio/auth/index.ts) +- [Signalwire](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/signalwire/auth/index.ts) - [SMTP](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/smtp/auth/index.ts) ### Without authentication @@ -60,6 +61,7 @@ If you are developing a webhook-based trigger, you need to ensure that the webho - [Search tweets - Twitter](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/twitter/triggers/search-tweets/index.ts) - [New issues - Github](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/github/triggers/new-issues/index.ts) - [Receive SMS - Twilio](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/twilio/triggers/receive-sms/index.ts) +- [Receive SMS - Signalwire](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/signalwire/triggers/receive-sms/index.ts) - [New photos - Flickr](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/flickr/triggers/new-photos/index.ts) ### Pagination with ascending order diff --git a/packages/docs/pages/guide/available-apps.md b/packages/docs/pages/guide/available-apps.md index 5874ca0c..6f950856 100644 --- a/packages/docs/pages/guide/available-apps.md +++ b/packages/docs/pages/guide/available-apps.md @@ -17,6 +17,7 @@ Following integrations are currently supported by Automatisch. - [RSS](/apps/rss/triggers) - [Salesforce](/apps/salesforce/triggers) - [Scheduler](/apps/scheduler/triggers) +- [Signalwire](/apps/signalwire/triggers) - [Slack](/apps/slack/actions) - [SMTP](/apps/smtp/actions) - [Stripe](/apps/stripe/triggers) diff --git a/packages/docs/pages/public/favicons/signalwire.svg b/packages/docs/pages/public/favicons/signalwire.svg new file mode 100644 index 00000000..1dda2037 --- /dev/null +++ b/packages/docs/pages/public/favicons/signalwire.svg @@ -0,0 +1 @@ + \ No newline at end of file