Twilio Autopilot Dynamic Say Action with variables from memory - twilio

I have a situation where I'm able to store additional details about a customer in memory . But Im unable to access them in the Task Definition .
E.g I'm storing the customer's last purchase amount and last purchase date to be presented if the customer wants to hear it as follows
{
"actions": [
{
"remember": {
"last_Purchase": "17585",
"last_Date":"25-Dec-2020"
}
},
{
"listen":True
}
]
}
They get stored in the memory . However I'm unable to use this in a subsequent task (I'm not using it in the same task as Twilio doesnt supports it ) .
In a subsequent task I want to be able to create a dynamic Say in a task as follows
Dear Customer your last purchase is {memory.last_Purchase} on {memory.last_Date}.
But I guess the syntax is wrong or the way I'm accessing the memory variable is wrong .
Request guidance .

Twilio developer evangelist here.
In JavaScript, you'll need to put a money sign ("$") in front of the brackets surrounding your variable name, so your Say Action would look something like this in JavaScript, like in a Twilio Function:
say = {
"say": `Dear Customer your last purchase is ${memory.last_Purchase} on ${memory.last_Date}.`
}
Additionally, objects saved with the Remember Action are placed at the top-level of the Memory object, so make sure you pull it out with JSON.parse:
let memory = JSON.parse(event.Memory);
The total JS code (say, in a Twilio Function) would look something like
exports.handler = function(context, event, callback) {
let actions = [];
let say = {};
let memory = JSON.parse(event.Memory);
say = {
"say": `Dear Customer your last purchase is ${memory.last_Purchase} on ${memory.last_Date}.`
}
actions.push(say);
let respObj = {
"actions": actions
};
callback(null, respObj);
};
Of course, alternatively, you could use
say = {
"say": "Dear Customer your last purchase is " + memory.last_Purchase + "on
" + memory.last_Date
}

Related

Include multiple incoming SMS messages/responses with Twilio functions

I'm working on a project now within Twilio, using Twilio Functions, where I'm trying to set up SMS messaging so that if we receive an incoming keyword, we respond with a specific message, including a URL. The plan is to have multiple incoming keywords, with different responses so if someone sends an SMS to one of our numbers, depending on that key word, we respond with a basic message and a URL. I'm trying to figure out the best way to handle this within Twilio Functions.
I have this working for a single incoming keyword/response, as seen below.
if (incomingMessage.includes('testpark')) {
twiml.message('StartMyParking:\n\nTo start your parking, please click this link: https://blahblah.com');
} else if (incomingMessage.includes('bye')) {
twiml.message('Goodbye!');
} else {
twiml.message('Please check your zone/code and try again.');
}
While that works, I want to add in more incoming words, along with responses, such as an incoming message of 'testpark2' and a response of 'StartMyParking:\n\nTo start your parking, please click this link: https://blahblah2.com'.
Then I would want to include another one with 'testpark3' and a response of 'StartMyParking:\n\nTo start your parking, please click this link: https://blahblah3.com' and so on, all within the same script.
Can someone help me understand how to achieve this?
There are a lot of ways to achieve your desired outcome, but here's the most straightforward to begin with.
Instead of creating an else if statement for every possible keyword, you could define the keyword/response pairs up front using a JavaScript Map.
The keys of the Map will be your keywords, the values of the Map will be your responses:
const keywordResponseMap = new Map([
['testpark2', 'StartMyParking:\n\nTo start your parking, please click this link: https://blahblah2.com'],
['testpark3', 'StartMyParking:\n\nTo start your parking, please click this link: https://blahblah3.com'],
['testpark', 'StartMyParking:\n\nTo start your parking, please click this link: https://blahblah.com'],
]);
const keywords = Array.from(keywordResponseMap.keys());
let keyword;
if (incomingMessage.includes('bye')) {
twiml.message('Goodbye!');
}
else if (keyword = keywords.find(k => incomingMessage.includes(k))) {
const response = keywordResponseMap.get(keyword);
twiml.message(response);
} else {
twiml.message('Please check your zone/code and try again.');
}
Also note that I'm putting the bye case up front because it is more performant than looking for the keywords in the incomingMessage, thus you avoid unnecessarily doing that processing when a user says bye.
You can use find to search for any keyword that is in the incomingMessage, then you can use the keyword that you found to retrieve the response from the map.
If your response will always be the same except for the URL, you could further optimize this by only storing the URL in the map and using string interpolation like this:
const keywordUrlMap = new Map([
['testpark2', 'https://blahblah2.com'],
['testpark3', 'https://blahblah3.com'],
['testpark', 'https://blahblah.com'],
]);
const keywords = Array.from(keywordUrlMap.keys());
let keyword;
if (incomingMessage.includes('bye')) {
twiml.message('Goodbye!');
}
else if (keyword = keywords.find(k => incomingMessage.includes(k))) {
const url = keywordUrlMap.get(keyword);
twiml.message(`StartMyParking:\n\nTo start your parking, please click this link: ${url}`);
} else {
twiml.message('Please check your zone/code and try again.');
}
It is also important to note that I'm putting testpark last in the map because testpark matches to testpark2 and testpark3. If you'd put it first, it would always resolve to testpark even with a user submits testpark2 or similar values.
Also, I'm using the Map type because it guarantees the order in which the keys are returned, which is again important for the previous point.
When you have a lot more keywords and responses, you may have to start looking at a solution to store them externally like a database, and query the database by keyword to resolve the response.
Good luck, we can't wait to see what you build!

Instagram Graph API Recent Search result returns blank data

thank you for reviewing my question.
I've been using Instagram Graph API to make some hashtag recent search.
# Retrieve Keys
key = json.loads(keycontent)
HASHTAG_ID = key['HASHTAG_ID']
USER_ID = key['USER_ID']
ACCESS_TOKEN = key['ACCESS_TOKEN']
CURSOR = key['CURSOR']
topic = 'HASHTAG_ID' # Job
# Get Request URL
url = f"https://graph.facebook.com/{HASHTAG_ID}/recent_media?fields=id,permalink,caption,media_url&limit=50&user_id={USER_ID}&access_token={ACCESS_TOKEN}"
if CURSOR != "":
url = url + '&after=' + CURSOR
res = requests.get(url)
print(res.json()[‘data’])
It works quite successfully, but problem turns out that it starts to return blank data after calling the function several times. The data I am receiving at the moment are equal to either of the followings:
{"data": []}
{
"data": [
],
"paging": {
"cursors": {
"after": "NEXT_CURSOR"
},
"next": "LINK_WITH_NEXT_CURSOR"
}
}
I've checked several known issues, and the list of what I've checked are stated below.
It is not the permission issue. I've checked all the permissions related, and it is confirmed that the app has all permission it needs.
The app is certainly below the execution limit. Actually, it is mostly even below half of it.
The app is also below the number of hashtags I can search. I've called significantly lower than 30 hashtags within 7 days.
So, I'd like to know if there are potential reasons that I am having blank data for Instagram Graph API call.
Thank you in advance.

How to retrieve Slack messages via API identified by permalink?

I'm trying to retrieve a list of Slack reminders, which works fine using Slack API's reminders.list method. However, reminders that are set using SlackBot (i.e. by asking Slackbot to remind me of a message) return the respective permalink of that message as text:
{
"ok": true,
"reminders": [
{
"id": "Rm012C299C1E",
"creator": "UV09YANLX",
"text": "https:\/\/team.slack.com\/archives\/DUNB811AM\/p1583441290000300",
"user": "UV09YANLX",
"recurring": false,
"time": 1586789303,
"complete_ts": 0
},
Instead of showing the permalink, I'd naturally like to show the message I wanted to be reminded of. However, I couldn't find any hints in the Slack API docs on how to retrieve a message identified by a permalink. The link is presumably generated by chat.getPermalink, but there seems to be no obvious chat.getMessageByPermalink or so.
I tried to interpet the path elements as channel and timestamp, but the timestamp (transformed from the example above: 1583441290.000300) doesn't seem to really match. At least I don't end up with the message I expected to retrieve when passing this as latest to conversations.history and limiting to 1.
After fiddling a while longer, here's how I finally managed in JS:
async function downloadSlackMsgByPermalink(permalink) {
const pathElements = permalink.substring(8).split('/');
const channel = pathElements[2];
var url;
if (permalink.includes('thread_ts')) {
// Threaded message, use conversations.replies endpoint
var ts = pathElements[3].substring(0, pathElements[3].indexOf('?'));
ts = ts.substring(0, ts.length-6) + '.' + ts.substring(ts.length-6);
var latest = pathElements[3].substring(pathElements[3].indexOf('thread_ts=')+10);
if (latest.indexOf('&') != -1) latest = latest.substring(0, latest.indexOf('&'));
url = `https://slack.com/api/conversations.replies?token=${encodeURIComponent(slackAccessToken)}&channel=${channel}&ts=${ts}&latest=${latest}&inclusive=true&limit=1`;
} else {
// Non-threaded message, use conversations.history endpoint
var latest = pathElements[3].substring(1);
if (latest.indexOf('?') != -1) latest = latest.substring(0, latest.indexOf('?'));
latest = latest.substring(0, latest.length-6) + '.' + latest.substring(latest.length-6);
url = `https://slack.com/api/conversations.history?token=${encodeURIComponent(slackAccessToken)}&channel=${channel}&latest=${latest}&inclusive=true&limit=1`;
}
const response = await fetch(url);
const result = await response.json();
if (result.ok === true) {
return result.messages[0];
}
}
It's not been tested to the latest extend, but first results look alright:
The trick with the conversations.history endpoint was to include the inclusive=true parameter
Messages might be threaded - the separate endpoint conversations.replies is required to fetch those
As the Slack API docs state: ts and thread_ts look like timestamps, but they aren't. Using them a bit like timestamps (i.e. cutting off some characters at the back and inserting a dot) seems to work, gladly, however.
Naturally, the slackAccessToken variable needs to be set beforehand
I'm aware the way to extract & transform the URL components in the code above might not the most elegant solution, but it proves the concept :-)

Forward Twilio call only during business hours

I've spent quite a few hours trying to find this and it still baffles me.
Am I able to only forward calls to my cell phone at certain hours, else voicemail? On the surface, this should be so simple - using a Twiml, perhaps? But I can't seem to "get it".
Thanks,
Nancy
Twilio Developer Evangelist here.
You can indeed do this with Twilio, but you will need to write some code and deploy it on the internet somewhere. Let's go through it.
Let's suppose that you're using this TwiML to forward your calls:
<Response>
<Dial>+5551234567</Dial>
</Response>
And you want to use the Say TwiML verb during 'out of hours' time:
<Response>
<Say>The office is currently closed.</Say>
</Response>
What you need is some application that can choose between them. For example, a simple Ruby and Sinatra application would look like this:
require 'sinatra'
post '/voice' do
content_type 'text/xml'
if Time.now.hour > 8 && Time.now.hour < 18
"<Response>
<Dial>+5551234567</Dial>
</Response>"
else
"<Response>
<Say>The office is currently closed.</Say>
</Response>"
end
end
Notice we're only looking at the time, not the day of the week. So you'll get calls sent between 8am and 6pm. You may want to make this a little more sophisticated depending on your needs.
You then just need to provide the URL for this application to Twilio. Depending on what tools you have available, you could run this on your own server, or on some cloud service provider such as Heroku, EngineYard, AppFog, etc. Most of these have very good documentation on how to deploy an application.
Hope this helps!
With Twilio functions this is easier now. You don't have to host anything anywhere. Just create a new Function linked to Incoming Voice Call events, then set your provisioned number to call that Function when a new call arrives. Here's a working example you can modify to fit your preferences; it includes dialing extensions but you can remove that if you want.
Note that it doesn't have DST logic and if you want that then you'll have to do some additional work with the moment() library.
https://gist.github.com/ChristopherThorpe/521bfdbd903ac7628ac01f8bb1d651a5
exports.handler = function(context, event, callback) {
const moment = require('moment');
//// Useful for debugging
//const util = require('util');
//console.log(util.inspect(context.getTwilioClient()));
//console.log(util.inspect(event));
// be sure to update numDigits below to match, or delete it for variable length
let phoneBook = {
"888" : "+1-800-800-8000", // super 8 motel
"666" : "+1-800-466-8356", // motel 6
"000" : null
};
let callerId = event.Caller; // || "+1-000-000-0000"; // default caller ID
let digits = event.Digits;
let twiml = new Twilio.twiml.VoiceResponse();
if (digits && phoneBook[digits]) {
twiml.say("Dialing extension " + digits);
twiml.dial({ callerId: callerId }, phoneBook[digits]);
twiml.hangup();
}
// Twilio time is in UTC. This allows 10 am to 7 pm PDT, or 9 am to 6 pm PST, weekdays.
// Twilio doesn't seem to have https://momentjs.com/timezone/ installed.
if ((moment().hour() >= 17 || moment().hour() < 2) && moment().isoWeekday() <= 5) {
let gather = twiml.gather({ numDigits: 3, timeout: 3 });
gather.say("Thank you for calling COMPANY NAME. Please dial your party's extension, or, hold, to leave a message.");
} else {
twiml.say("Thank you for calling COMPANY NAME. You have reached us outside business hours.");
}
twiml.redirect("http://twimlets.com/voicemail?Email=[YOUR-EMAIL]&Message=Please%20leave%20a%20message.&Transcribe=true");
callback(null, twiml);
};

Titanium ti.storekit returns empty products

I am using ti.storekit to do in app billing. Everything seems to work, but when I run the following code:
Storekit.requestProducts(["FooPro"], function (evt)
{
Ti.API.info('evt:' + JSON.stringify(evt, undefined, 2));
//hideLoading();
if (!evt.success)
{
alert('ERROR: We failed to talk to Apple!');
}
else if (evt.invalid)
{
alert('ERROR: We requested an invalid product!');
}
else
{
item = evt.products;
Ti.API.info('gotProducts:' + JSON.stringify(item, undefined, 2));
success(item[0]);
}
});
I get a successful response, but with no products:
evt:{
"type": "callback",
"products": [
{}
],
"source": {},
"success": true
}
So there is no products, and just silently fails. In iTunes connect, in the product, i have an in app product with the product id of FooPro (although it is pending review). I am not sure what else I am doing wrong. everything seems to work, i just get back an empty products array and I cant figure out why.
This is also causing the error so that when I call
// product = {} since the above returned it as empty
Storekit.purchase(product);
it errors and tells me this as well:
Passing individual args to `purchase` is DEPRECATED. Call `purchase` passing in a dictionary of arguments.
addTransactionObserver` should be called before `purchase`
EDIT: the products, when using Ti.API.info() to output the contents show as empty. Yet after signing into itunes with the test account on the device, the products still do show up as empty, but when I do more debugging, the product object seems to actually be:
{'My product description'}
So when I call
Storekit.purchase(product)
It seems like it's really calling
Storekit.purchase({'My product description'})
So it appears to be failing here because the purchase call never triggers the transactionState event listener.
Environment:
- iPhone: 5, 4s
Thanks for any help.
Whow... I've been playing around with it further, and there is something weired going on...
Even when you get [{}] for products, products[0].title brings back the actual product title.
It's as though it's there, but hiding from the trace statement.
All you then needed to do is send {product:e.products[0]} to purchase, and voila :
Storekit.requestProducts(productIDs, function(e) {
Ti.API.info("GOT PRODUCTS : "+JSON.stringify(e));
// 'GOT PRODUCTS : {"type":"callback","products":[{}],"source":{},"success":true}'
Ti.API.info('PROD TITLE : '+e.products[0].title);
// 'PROD TITLE : My IAP product'
Storekit.purchase({product:e.products[0]});
// You need to implement 'transactionState' and addTransactionObserver to carry on from here - see the example code in the module package for that
});
And make sure you set Storekit.autoFinishTransactions = false; - or you won't get the final purchase event after the user has bought your product.

Resources