diff --git a/voicemail/README.md b/voicemail/README.md index 711633d0..9e61287b 100644 --- a/voicemail/README.md +++ b/voicemail/README.md @@ -46,7 +46,7 @@ twilio serverless:init sample --template=voicemail && cd sample twilio serverless:start --ngrok ``` -5. Set your incoming call webhook URL for the phone number you want to configure to `https://.ngrok.io/voicemail +5. Set your incoming call webhook URL for the phone number you want to configure to `https://.ngrok.io/voicemail` ℹ️ Check the developer console and terminal for any errors, make sure you've set your environment variables. diff --git a/voicemail/changelog.md b/voicemail/changelog.md index 3982d461..144828c2 100644 --- a/voicemail/changelog.md +++ b/voicemail/changelog.md @@ -3,6 +3,7 @@ ## [Unreleased] ## [1.0.0] + ### Added -- Initial release. +- Initial release. diff --git a/voicemail/functions/recording.js b/voicemail/functions/recording.js index 6a2f9a19..742c31e2 100644 --- a/voicemail/functions/recording.js +++ b/voicemail/functions/recording.js @@ -1,3 +1,32 @@ +/* + * recording.js + * Description: + * This Twilio Function is executed when a voicemail + * recording has been processed by Twilio. This + * recording is initiated in the voicemail function + * located in voicemail.protected.js. Once the recording + * is finished and processed, it will send the + * generated recording URL to MY_PHONE_NUMBER + * specified in /.env. + * + * Contents: + * 1. Main Handler + */ + +/* + * 1. Main Handler + * + * This is the entry point to your Twilio Function, + * which will send the provided recording URL as an SMS + * to MY_PHONE_NUMBER specified in /.env. + * + * The function will fetch the Twilio Client provided by + * Functions. The Twilio client will be used to find the Twilio Number + * the voicemail was left at. It will then use the Twilio Client + * send an SMS of the recording URL from that number to + * MY_PHONE_NUMBER specified in /.env. + */ + exports.handler = async function (context, event, callback) { const client = context.getTwilioClient(); diff --git a/voicemail/functions/voicemail.protected.js b/voicemail/functions/voicemail.protected.js index 657ff790..3dc4387e 100644 --- a/voicemail/functions/voicemail.protected.js +++ b/voicemail/functions/voicemail.protected.js @@ -1,10 +1,54 @@ +/** + * Call Forwarding with Voicemail + * + * Description: + * This file contains the main Twilio Function that forwards + * incoming calls to a specific phone number during a set work + * hours and records voicemail for calls that are unanswered or + * outside of the work hours. + * + * Contents: + * 1. Dependencies + * 2. Configuration + * 3. Helper Function + * 4. Main Handler + */ + +/** + * 1. Dependencies + * These dependecies will be used in the main handler to assist + * in formatting the date and forwaring URL for the recorded voicemail. + */ + const moment = require('moment'); const url = require('url'); +/* + * 2. Configuration + * + * Contains greeting messages which is spoken when a call + * is received out of work hours. Here you can change the default + * greetings, voice types, and add addional greetings for specified languages. + * + * The function chooses the greeting based on the FromCountry parameter provided + * by Twilio when the call request is sent to the function. + * + * You can also change the default UTC time offset, work hours + * start and end times, start and end days of the work week. + * These values are only used if they arent specified in /.env. + * + */ + const GREETINGS = { + // default greeting if there isn't one set for the callers FromCountry parameter _default: { + // message that is spoken to the caller text: 'Hi there! You are calling after my work hours. Please leave a message after the beep', + + // language code for conversion of text-to-speech messages, e.g. 'en' or 'en-gb' language: 'en-US', + + // voice for text-to-speech messages, one of 'man', 'woman' or any supported Amazon Polly or Google voices voice: 'Polly.Joey', }, DE: { @@ -14,13 +58,28 @@ const GREETINGS = { }, }; +/* + * default values to be used if they aren't provided as environment variables + * in /.env. + */ const DEFAULT_UTC_OFFSET = 0; const DEFAULT_WORK_WEEK_START = 1; // Monday const DEFAULT_WORK_WEEK_END = 5; // Friday const DEFAULT_WORK_HOUR_START = 8; // 8:00, 8AM const DEFAULT_WORK_HOUR_END = 18; // 18:59, 6:59PM -function getInteger(stringValue, defaultValue) { +/* + * 2. Helper Function + * + * Helper function to parse string values to integers. + * Returns default value if string cannot parsed to an integer. + * + * stringValue - value to be converted into an integer + * defaultValue - default value to return if stringValue + * can't be parse into integer + */ + +function parseInteger(stringValue, defaultValue) { const parsedNumber = parseInt(stringValue, 10); if (isNaN(parsedNumber)) { return defaultValue; @@ -28,30 +87,59 @@ function getInteger(stringValue, defaultValue) { return parsedNumber; } +/* + * 3. Main Handler + * + * This is the entry point to your Twilio Function, + * which will create a new Voice Response using Twiml based on + * the current time and defined work hours. If the call is + * during work hours the Voice Response will forward the call + * and dial the MY_PHONE_NUMBER specified in /.env. + * If the call is placed outside work hours, the Voice Response + * will respond to the caller with a greeting and will record a voicemail + * which will be forwarded to the recording function in recording.js. + * + * The callback will be used to return from your function + * with the Twiml Voice Response you defined earlier. + * In the callback in non-error situations, the first + * parameter is null and the second parameter + * is the value you want to return. + */ + exports.handler = function (context, event, callback) { + // parse the environment variables and get the work hours and timezone const phoneNumberToForwardTo = context.MY_PHONE_NUMBER; - const timezone = getInteger(context.TIMEZONE_OFFSET, DEFAULT_UTC_OFFSET); + const timezone = parseInteger(context.TIMEZONE_OFFSET, DEFAULT_UTC_OFFSET); const workWeek = { - start: getInteger(context.WORK_WEEK_START, DEFAULT_WORK_WEEK_START), - end: getInteger(context.WORK_WEEK_END, DEFAULT_WORK_WEEK_END), + start: parseInteger(context.WORK_WEEK_START, DEFAULT_WORK_WEEK_START), + end: parseInteger(context.WORK_WEEK_END, DEFAULT_WORK_WEEK_END), }; const workHour = { - start: getInteger(context.WORK_HOUR_START, DEFAULT_WORK_HOUR_START), - end: getInteger(context.WORK_HOUR_END, DEFAULT_WORK_HOUR_END), + start: parseInteger(context.WORK_HOUR_START, DEFAULT_WORK_HOUR_START), + end: parseInteger(context.WORK_HOUR_END, DEFAULT_WORK_HOUR_END), }; + // calculate the current day and time according to the timezone const currentTime = moment().utcOffset(timezone); const hour = currentTime.hour(); const day = currentTime.day(); + + // check if there is a translated greeting for callers country const translatedGreeting = GREETINGS[event.FromCountry]; const hasTranslatedGreeting = typeof translatedGreeting !== 'undefined'; + // between monday and friday const isWorkingDay = day <= workWeek.end && day >= workWeek.start; // between 8am and 7pm const isWorkingHour = hour <= workHour.end && hour >= workHour.start; + // create a new TwiML response const twiml = new Twilio.twiml.VoiceResponse(); + /* + * If the current time is within work hours, forward the call to the specified phone number. + * Else play the greeting message and begin recording for a voicemail message. + */ if (isWorkingDay && isWorkingHour) { twiml.dial(phoneNumberToForwardTo); } else { @@ -76,5 +164,6 @@ exports.handler = function (context, event, callback) { action: url.resolve(context.PATH, 'recording'), }); } + // return the generated Twilio Voice Response callback(null, twiml); }; diff --git a/voicemail/package.json b/voicemail/package.json index 6fa6ae79..47bb34ea 100644 --- a/voicemail/package.json +++ b/voicemail/package.json @@ -2,6 +2,6 @@ "version": "1.0.0", "private": true, "dependencies": { - "moment": "^2.24.0" + "moment": "^2.30.1" } }