I'm new to Twilio Studio, Functions, and by extension node.js . I'm trying to create a function that will evaluate the current day and time. If that time is outside the window i want to return false, otherwise true. This is what i have so far:
exports.handler = function(context, event, callback) {
let twiml = new Twilio.twiml.VoiceResponse();
var day = Twilio.Date.toString();
twiml.say(day);
callback(null, twiml);
};
take a look at the below Twilio Function, and modify accordingly.
// Time of Day Routing
// Useful for IVR logic, for Example in Studio, to determine which path to route to
// Add moment-timezone 0.5.31 as a dependency under Functions Global Config, Dependencies
const moment = require('moment-timezone');
exports.handler = function(context, event, callback) {
let twiml = new Twilio.twiml.VoiceResponse();
function businessHours() {
// My timezone East Coast (other choices: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones)
const now = moment().tz('America/New_York');
// Weekday Check using moment().isoWeekday()
// Monday = 1, Tuesday = 2 ... Sunday = 7
if(now.isoWeekday() <= 5 /* Check for Normal Work Week Monday - Friday */) {
//Work Hours Check, 9 am to 5pm (17:00 24 hour Time)
if(now.hour() >= 9 && now.hour() < 17 /* 24h basis */) {
return true
}
}
// Outside of business hours, return false
return false
};
const isOpen = businessHours();
if (isOpen) {
twiml.say("Business is Open");
} else {
twiml.say("Business is Closed");
}
callback(null, twiml);
};
Related
fine people of Stack Overflow.
I'm trying to solve a problem I'm having involving twilio functions, messaging services, and databases.
What I'm attempting to do is send a message to all members of a database at once.
My code is a mess, as Javascript isn't my native language and I'm rather new to twilio.
The problem I believe I'm having is with the async/await feature of javascript.
Here is my code so far:
// Boiler Plate Deta Code
const { Deta } = require("deta");
// Function to access database and get object of conta
async function FetchDB(db) {
let res = await db.fetch();
allItems = res.items;
// continue fetching until last is not seen
while (res.last){
res = await db.fetch({}, {last: res.last});
allItems = allItems.concat(res.items);
}
}
// Function to get total number of contacts.
async function ReturnNumberOfContacts(allItems) {
number_of_contacts = allItems.length;
}
// Function to send message to contact in database.
async function SendMessages(allItems, message) {
allItems.forEach(contact => {
let users_name = contact.name
client.messages
.create({
body: `Hey ${users_name}! ${message}`,
messagingServiceSid: messaging_service,
to: contact.key
})
});
}
// Function to submit response to broadcaster.
async function SuccessResponse(user_name, number_of_contacts) {
responseObject = {
"actions": [
{
"say": `${user_name}, your broadcast has successfully sent to ${number_of_contacts} contacts.`
},
{
"listen": true
}
]
}
}
// Main Function
exports.handler = async function(context, event, callback) {
// Placeholder for number of contacts
let number_of_contacts;
// Place holder for object from database of all contacts
let allItems;
// Placeholder for users message
let message;
// Placeholder for response to user
let responseObject;
//Twilio and Deta, Etc Const
const client = require('twilio')(context.ACCOUNT_SID, context.AUTH_TOKEN);
const deta = Deta(context.DETA_PROJECT_KEY);
const db = deta.Base("users2");
const messaging_service = context.MESSAGING_SERVICE;
// From Phone Number
const from = event.UserIdentifier;
// Parse memory
const memory = JSON.parse(event.Memory);
// Fetch all items from database and return total number of contacts.
// Update relavent variables
await FetchDB(db, allItems).then(ReturnNumberOfContacts(allItems));
// Figure out if message came from short circuit broadcast or normal
if (memory.triggered) {
message = memory.message;
} else {
message = memory.twilio.collected_data.broadcast_message.answers.message_input.answer;
}
// Check if verified and set name.
const current_user = await db.get(from);
// Get the current users name or set a default value
let user_name = current_user.name || "friend";
// Determine if user is an authorized broadcaster
if (from === context.BROADCAST_NUMBER) {
// Decide if the sending of a message should be cancelled.
if (message.toLowerCase() === "c" || message.toLowerCase() === "cancel") {
responseObject = {
"actions": [
{
"say": `${user_name}, you have canceled your request and no messages have been sent.`
},
{
"listen": false
}
]
}
// Return Callback and end task
callback(null, responseObject);
}
// Move forward with sending a message.
else {
// Send message to users in database and send success message to broadcaster.
await SendMessages(message, client, messaging_service)
.then(SuccessResponse(user_name, number_of_contacts))
return callback(null, responseObject);
}
// The user is not authorized so return this.
}
return callback(null, {
"actions": [
{
"say": "You are not authorized to broadcast."
},
{
"listen": false
}
]
})
};
So when the Fetch() function is triggered, I want the database to load a list of everyone and have twilio send them the desired message saved in the message variable. I have the code working so that I can read from the database and get the proper values, and send a single text message with the desired message, but the problem I'm having now is integrating it all together.
Thanks if anyone can point me in the right direction here.
Again, I'm new to javascript and more specifically asynchronous programming.
Twilio developer evangelist here.
The issue from the error says that allItems is undefined when you call ReturnNumberOfContacts.
I think the issue comes from trying to use allItems as a sort of global variable, same for number_of_contacts. It would be better for FetchDB to resolve with the list of items and ReturnNumberOfContacts to resolve with the number of the items.
You also have some arguments missing when you call SendMessages in your function. I've updated it to the point that I think it will work:
// Boiler Plate Deta Code
const { Deta } = require("deta");
// Function to access database and get object of conta
async function FetchDB(db) {
let res = await db.fetch();
let allItems = res.items;
// continue fetching until last is not seen
while (res.last) {
res = await db.fetch({}, { last: res.last });
allItems = allItems.concat(res.items);
}
return allItems;
}
// Function to send message to contact in database.
async function SendMessages(allItems, message, client, messagingService) {
return Promise.all(
allItems.map((contact) => {
let usersName = contact.name;
return client.messages.create({
body: `Hey ${usersName}! ${message}`,
messagingServiceSid: messagingService,
to: contact.key,
});
})
);
}
// Main Function
exports.handler = async function (context, event, callback) {
// Placeholder for users message
let message;
//Twilio and Deta, Etc Const
const client = require("twilio")(context.ACCOUNT_SID, context.AUTH_TOKEN);
const deta = Deta(context.DETA_PROJECT_KEY);
const db = deta.Base("users2");
const messagingService = context.MESSAGING_SERVICE;
// From Phone Number
const from = event.UserIdentifier;
// Parse memory
const memory = JSON.parse(event.Memory);
// Fetch all items from database and return total number of contacts.
// Update relavent variables
const allItems = await FetchDB(db);
const numberOfContacts = allItems.length;
// Figure out if message came from short circuit broadcast or normal
if (memory.triggered) {
message = memory.message;
} else {
message =
memory.twilio.collected_data.broadcast_message.answers.message_input
.answer;
}
// Check if verified and set name.
const currentUser = await db.get(from);
// Get the current users name or set a default value
let userName = currentUser.name || "friend";
// Determine if user is an authorized broadcaster
if (from === context.BROADCAST_NUMBER) {
// Decide if the sending of a message should be cancelled.
if (message.toLowerCase() === "c" || message.toLowerCase() === "cancel") {
// Return Callback and end task
callback(null, {
actions: [
{
say: `${userName}, you have canceled your request and no messages have been sent.`,
},
{
listen: false,
},
],
});
}
// Move forward with sending a message.
else {
// Send message to users in database and send success message to broadcaster.
await SendMessages(allItems, message, client, messagingService);
return callback(null, {
actions: [
{
say: `${userName}, your broadcast has successfully sent to ${numberOfContacts} contacts.`,
},
{
listen: true,
},
],
});
}
// The user is not authorized so return this.
}
return callback(null, {
actions: [
{
say: "You are not authorized to broadcast.",
},
{
listen: false,
},
],
});
};
What I did here was change FetchDB to only take the db as an argument, then to create a local allItems variable that collects all the contacts and then returns them.
async function FetchDB(db) {
let res = await db.fetch();
let allItems = res.items;
// continue fetching until last is not seen
while (res.last) {
res = await db.fetch({}, { last: res.last });
allItems = allItems.concat(res.items);
}
return allItems;
}
This is then called in the main body of the function to assign a local variable. I also replaced the ReturnNumberOfContacts function with a simple assignment.
const allItems = await FetchDB(db);
const numberOfContacts = allItems.length;
One thing you may want to consider is how many contacts you are trying to send messages to during this function. There are a few limits you need to be aware of.
Firstly, Function execution time is limited to 10 seconds so you need to make sure you can load and send all your messages within that amount of time if you want to use a Twilio Function for this.
Also, there are limits for the number of concurrent connections you can make to the Twilio API. That limit used to be 100 connections per account, but it may vary these days. When sending asynchronous API requests as you do in JavaScript, the platform will attempt to create as many connections to the API that it can in order to trigger all the requests asynchronously. If you have more than 100 contacts you are trying to send messages to here, that will quickly exhaust your available concurrent connections and you will receive 429 errors. You may choose to use a queue, like p-queue, to ensure your concurrent connections never get too high. The issue in this case is that it will then take longer to process the queue which brings me back to the original limit of 10 seconds of function execution.
So, I think the above code may work in theory now, but using it in practice may have other issues that you will need to consider.
I am new to Twilio and want to include a kind of switch in my Studio Flow Chart that checks if we are in the defined business hours. If yes (success), it forwards to the number, if no (fail), it forwards to voicemail.
I tried to build a function, somewhat derived from this twilio git: https://github.com/twilio-labs/function-templates/tree/main/voicemail.
const moment = require('moment');
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) {
const parsedNumber = parseInt(stringValue, 10);
if (isNaN(parsedNumber)) {
return defaultValue;
}
return parsedNumber;
}
exports.handler = function(context, event, callback) {
const timezone = getInteger(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),
};
const workHour = {
start: getInteger(context.WORK_HOUR_START, DEFAULT_WORK_HOUR_START),
end: getInteger(context.WORK_HOUR_END, DEFAULT_WORK_HOUR_END),
};
const currentTime = moment().utcOffset(timezone);
const hour = currentTime.hour();
const day = currentTime.day();
// between monday and friday
const isWorkingDay = day <= workWeek.end && day >= workWeek.start;
// between 8am and 7pm
const isWorkingHour = hour <= workHour.end && hour >= workHour.start;
if (isWorkingDay && isWorkingHour) {
return true;
} else {
return false;
}
};
From the error I get (82002 – runtime application timed out), the callback must be different. What is the correct way for callback(err, response)?
Thanks.
Twilio developer evangelist here.
In a Twilio Function, in order to return a result you must call the callback function that is passed in as the third parameter to your handler function. You are getting a timeout here because you never call the callback so the function runs for 10 seconds then times out.
I'd try returning the data like this:
if (isWorkingDay && isWorkingHour) {
return callback(null, true);
} else {
return callback(null, false);
}
};
Note that it might not like primitive boolean values, so if that doesn't work, try a JavaScript object like:
if (isWorkingDay && isWorkingHour) {
return callback(null, { inWorkingTime: true });
} else {
return callback(null, { inWorkingTime: false });
}
};
We currently use Twilio Conference, however we would like to allow certain participants based on their caller-IDs, rest to be rejected. Currently anyone who dials the number for conference can participate and this is not something we want due to privacy, security and compliance.
How can we do it with TwiML?
Best,
Savas
You cannot do this with TwiML Bins (static TwiML) but you can use a Twilio Function for this or even Twilio Studio with the split based on Widget, to determine if the trigger.message.From number is allowed.
Below is an example Twilio Function you can modify for your purpose. Currently, it only allows certain CallerID in as moderators and provides these numbers the ability to start a conference, others can only be participants in an active conference. You can see the steps to set-up a Twilio Function here (just apply similar steps to setting up the code below). You can extend the function and return a Twilio TwiML Reject verb in the participant path for non-moderators.
exports.handler = function(context, event, callback) {
let twiml = new Twilio.twiml.VoiceResponse();
let callerId = event.From || null;
let conferenceParams = {};
let conferenceName = "My Conference Room";
let moderators = ["+1813279xxxx", "+1813393xxxx", "+1813918xxxx"];
conferenceParams.beep = true;
if (moderators.indexOf(callerId) === -1) {
conferenceParams.startConferenceOnEnter = false;
conferenceParams.endConferenceOnExit = false;
}
else
{
conferenceParams.startConferenceOnEnter = true;
conferenceParams.endConferenceOnExit = true;
}
twiml.dial().conference(conferenceParams, conferenceName);
callback(null, twiml);
};
Here is the approach you are looking for, both Functions are viable.
exports.handler = function(context, event, callback) {
let twiml = new Twilio.twiml.VoiceResponse();
let callerId = event.From || null;
let conferenceParams = {};
let conferenceName = "My Conference Room";
let moderators = ["+1678785xxxx", "+1813393xxxx", "+1813918xxxx"];
conferenceParams.beep = true;
if (moderators.indexOf(callerId) === -1) {
twiml.reject({reason: 'busy'});
callback(null, twiml);
}
else
{
conferenceParams.startConferenceOnEnter = true;
conferenceParams.endConferenceOnExit = true;
twiml.dial().conference(conferenceParams, conferenceName);
callback(null, twiml);
}
};
Im looking to invoke a twilio function and obtain a count of available workers.
I have a feeling it might be related to TaskQueues and the Matching Workers who are available?
Ive come up with the following. However, users are still listed as available when they are interacting with a task, which means this wont work necessarily.
exports.handler = function (context, event, callback) {
const client = require('twilio')(context.ACCOUNT_SID, context.AUTH_TOKEN);
client.taskrouter
.workspaces('eee')
.workers.list()
.then(workers => {
data = {
availWorkersCount: Object.keys(workers.filter(x=> x.available === true && x.attributes.includes("sales"))).length
};
const response = new Twilio.Response();
response.appendHeader('Access-Control-Allow-Origin', '*');
response.appendHeader('Access-Control-Allow-Methods', 'OPTIONS POST GET');
response.appendHeader('Access-Control-Allow-Headers', 'Content-Type');
response.appendHeader('Content-Type', 'application/json');
response.setBody(data);
callback(null, response);
});
};
I know this is a bit late, but this is the chunk of code I use in a twilio function to do just this. We call the function from the studio flow and use the results to decide if I am going to route the call into voicemail or play some IVR options for picking what queue they should be put in. To use it you can pass in an optional skill to filter down the agents even more. We grab all available workers then filter down to only the ones that have the needed skill.
You could then take this and check if any of the agents that are available also have a task assigned to them then remove them from the count.
const fetch = require("node-fetch");
exports.handler = function(context, event, callback) {
let response = new Twilio.Response();
// Set the status code to 200 OK
response.setStatusCode(200);
// Set the Content-Type Header
response.appendHeader('Access-Control-Allow-Origin', '*');
response.appendHeader('Access-Control-Allow-Methods', 'OPTIONS, POST, GET');
response.appendHeader('Access-Control-Allow-Headers', 'Content-Type');
response.appendHeader('Content-Type', 'application/json');
let body = {
TotalAvailable: 0
};
let client = context.getTwilioClient();
client.taskrouter.workspaces(context.WorkspaceSid)
.workers
.list({
available: 'true',
limit: 50
})
.then((workers) => {
let agents = [];
let i = 0;
if(workers){
for(i = 0; i < workers.length; i++){
let worker = workers[i];
let item = {};
let attributes = JSON.parse(worker.attributes);
if(attributes && attributes.routing && attributes.routing.skills && attributes.routing.skills.length > 0){
item.skills = attributes.routing.skills;
item.nid = attributes.nid;
item.first_name = attributes.first_name;
item.last_name = attributes.last_name;
if(event.skill){
if(item.skills.includes(event.skill)){
// TODO: filter here
agents.push(item);
}
}else{
agents.push(item);
}
}
}
}
body.TotalAvailable = agents.length;
body.Agents = agents;
response.setBody(body);
callback(null, response);
})
.catch((ex) => {
body.error = true;
body.message = ex;
response.setBody(body);
callback(null, response);
});
};
I am setting up a Sync Application using Twilio's Sync Library. For some reason, none of the REST API methods seem to work. That is, I cannot get any of the sync methods to console.log() anything via the runtime functions.
I can, however, console.log() plain text.
Here is my code:
exports.handler = function(context, event, callback) {
// 0. Init
// const phoneNumber = event.phoneNumber;
const issueLimit = 3; // CHANGE IF NEEDED
const listName = 'issues';
const twilioClient = context.getTwilioClient();
// 1. List all lists
twilioClient.sync.services(context.SYNC_SERVICE_SID)
.syncLists
.list({limit: 20})
.then(syncLists => syncLists.forEach(s => console.log(s.sid)));
// 2. return true if quota reached
console.log("Got to here");
// 3. return false
callback(null, undefined);
};
The only code that appears to execute is the 'console.log("Got to here");'. I'm also not receiving any error messages.
Any guidance is sincerely appreciated.
When you see .then(), that's a promise, and you can read more about this here https://www.twilio.com/blog/2016/10/guide-to-javascript-promises.html
In other words, the JavaScript engine goes to your steps 2. and then 3. without waiting for 1. to finish. And since you're returning at step 3 with callback(null, undefined); you won't see the logs.
So, you'll have to move callback() inside the .then(), something like this:
exports.handler = function (context, event, callback) {
// 0. Init
// const phoneNumber = event.phoneNumber;
const issueLimit = 3; // CHANGE IF NEEDED
const listName = 'issues';
const twilioClient = context.getTwilioClient();
// 1. List all lists
twilioClient.sync.services(context.SYNC_SERVICE_SID)
.syncLists
.list({ limit: 20 })
.then(
function (syncLists) {
console.log("Got to here");
syncLists.forEach(s => console.log(s.sid));
callback(null, undefined);
}
);
};