In Twilio Studio, I'm making a GET request and am trying to parse JSON and subsequently assign variables based on the parsed JSON. I'm having difficulty doing so with the JSON that is returned.
Essentially I'm trying to set variables from the "Row" that matches the returned JSON (a user dials in, enters their PIN {{widgets.PIN_Entry.Digits}}, the PIN will match a "Row" in the returned JSON from the GET request and we set variables for userID, userEmail, userName, userPin for the matched row).
{
"DataSource": {
"Id": "12345",
"Name": "Dial-In Subscribers",
"Rows": [
[
"EMP-0226",
"ron#pawneeil.com",
"Ron Swanson",
"00054321"
],
[
"EMP-0267",
"leslie#pawneeil.com",
"Leslie Knope",
"00012345"
]
],
"TotalRows": 2,
"LastUpdated": "2020-08-26T03:39:42.7670000Z",
"CompanyId": 12345
}
}
I can easily do this with JSON Path (not supported by Twilio studio) to select the values I'm looking to set as variables, but I can't figure out how to use Liquid to do this.
userID == $.DataSource.Rows[?(#.includes('00012345'))].[0]
(would return "EMP-0267")
userEmail == $.DataSource.Rows[?(#.includes('00012345'))].[1]
(would return "leslie#pawneeil.com")
userName == $.DataSource.Rows[?(#.includes('00012345'))].[2]
(would return "Leslie Knope)
userPin == $.DataSource.Rows[?(#.includes('00012345'))].[3]
(would return "00012345")
Can anyone share some ideas on how to parse the JSON and set variables using Liquid? Here's how I'm thinking I would accomplish this:
Match the variable {{widgets.PIN_Entry.Digits}} to a row in the returned JSON
Parse the selected row and set variables for userID, userEmail, userName, userPin.
I use the Run Function Widget in these cases, I find it much easier to deal with then the nuances of Liquid Syntax.
// Description
// Make a read request to an external API
// Add axios 0.20.0 as a dependency under Functions Settings, Dependencies
const axios = require('axios');
exports.handler = function (context, event, callback) {
let twiml = new Twilio.twiml.VoiceResponse();
// Arrays start at 0
let selectedDigit = 0;
axios
.get(`https://x.x.x.x/myAPI`)
.then((response) => {
let { Rows } = response.data.DataSource;
let result = Rows.filter((record, index) => index === selectedDigit);
twiml.say(`The result is ${result}`);
return callback(null, twiml);
})
.catch((error) => {
console.log(error);
return callback(error);
});
};
Related
I collect data from user input and to finish I want to calculate a value based on that input.
For example, I collect the person's weight and then the height to calculate the person's BMI, General Flow.
How can I calculate BMI in the last step and show the result to the user?
In addition to jess’ post, here's another approach you might try in order to calculate the BMI based on the user’s inputs. Here are the differences from the first approach provided:
Composite custom entities
This will allow you to create entities wherein you can easily extract the numbers provided by the user instead of getting a string and converting this string to a number in your webhook. With these entities, it’s not necessary to list every other option for height and weight.
Form Parameters
Instead of defining parameters in an intent adding the parameters, you can add the parameters in a page where the agent can interact with the end-user for multiple turns until the parameters are fulfilled.
Here’s the step-by-step procedure to implement these features.
Create composite custom entities in order to collect form parameters from the end-user for the page. You can design your custom entities as follow:
a. Create custom entities for height and weight unit names.
b. Then, create the composite custom entities containing the number and the unit names for each. Note that you should add an alias to ensure that these values will be returned individually.
Create an intent that will be used to trigger the start of the flow. Note to add enough training phrases for what end-users might type or say.
Create a page where you can transition from your Default Start Page when the Intent.BMI intent has been triggered. This page will also be used to collect the form parameters which you can use to calculate the BMI.
Create the flow by adding an intent route for Intent.BMI intent where the transition is BMI page. The flow would look like this.
Now, proceed to the BMI page and add the form parameters accordingly. Make sure to set these parameters as required. Add condition routes as well wherein you can return the response from your webhook once the parameters are fulfilled.
a. The BMI page may look like this.
b. For parameters, here’s an example on how to add these parameters.
c. For condition routes, I added a condition to return a response once the form parameters are fulfilled. If not yet fulfilled, the agent will keep prompting the user for a valid input. I used a webhook to return the responses wherein this webhook extracted the value for each parameter and was able to calculate the BMI.
In your webhook, create a function that will extract the form parameters and calculate the BMI based on these values. Here’s another example using Node.js.
index.js
'use strict';
const express = require('express');
const bodyParser = require('body-parser');
const app = express();
var port = process.env.PORT || 8080;
app.use(
bodyParser.urlencoded({
extended: true
})
);
app.use(bodyParser.json());
app.post('/BMI', (req, res) => processWebhook4(req, res));
var processWebhook4 = function(request, response ){
const params = request.body.sessionInfo.parameters;
var heightnumber = params["height.number"];
var weightnumber = params["weight.number"];
var heightunit = params["height.unit-height"]
var weightunit = params["weight.unit-weight"]
var computedBMI;
if (heightunit == "cm" && weightunit == "kg") { //using metric units
computedBMI = ((weightnumber/heightnumber/heightnumber )) * 10000;
} else if (heightunit == "in" && weightunit == "lb") { //using standard metrics
computedBMI = ((weightnumber/heightnumber/heightnumber )) * 703;
}
const replyBMI = {
'fulfillmentResponse': {
'messages': [
{
'text': {
'text': [
'This is a response from webhook! BMI is ' + computedBMI
]
}
}
]
}
}
response.send(replyBMI);
}
app.listen(port, function() {
console.log('Our app is running on http://localhost:' + port);
});
package.json
{
"name": "cx-test-functions",
"version": "0.0.1",
"author": "Google Inc.",
"main": "index.js",
"engines": {
"node": "8.9.4"
},
"scripts": {
"start": "node index.js"
},
"dependencies": {
"body-parser": "^1.18.2",
"express": "^4.16.2"
}
}
Here's the result.
In order to calculate the values of the input you collected from the bot, you will need to set-up a code using a webhook to calculate the BMI and connect the Webhook URL in the Dialogflow CX Console. Here’s a simple flow you can try:
First, create composite custom entities which can be used to match values in the training phrases in an intent, for example, weight and height. https://cloud.google.com/dialogflow/cx/docs/concept/entity#custom.
Then create an intent with training phrases that match the values
with the entities you created.
There are two ways to set the parameter values: Intent parameters and Form parameters. In my example, I used Intent parameters to get the parameter values that are stored when you query the flow of the conversation from the “Test Agent” section:
Then prepare your code in the webhook to process the values to calculate the BMI: https://cloud.google.com/dialogflow/cx/docs/concept/webhook. Here’s a sample code using NodeJS:
index.js
const express = require('express') // will use this later to send requests
const http = require('http') // import env variables
require('dotenv').config()
const app = express();
const port = process.env.PORT || 3000
/// Google Sheet
const fs = require('fs');
const readline = require('readline');
app.use(express.json())
app.use(express.urlencoded({ extended: true }))
app.get('/', (req, res) => { res.status(200).send('Server is working.') })
app.listen(port, () => { console.log(`🌏 Server is running at http://localhost:${port}`) })
app.post('/bmi', (request, response) => {
let params = request.body.sessionInfo.parameters;
let height = getNumbers(params.height); // 170 cm from the example
let weight = getNumbers(params.weight); // 60 kg from the example
let bmi = (weight/(height/100*height/100));
let fulfillmentResponse = {
"fulfillmentResponse": {
"messages": [{
"text": {
"text": [
bmi
]
}
}]
}
};
response.json(fulfillmentResponse);
});
// Extract number from string
function getNumbers(string) {
string = string.split(" ");
var int = "";
for(var i=0;i<string.length;i++){
if(isNaN(string[i])==false){
int+=string[i];
}
}
return parseInt(int);
}
package.json
{
"name": "server",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "node index.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"dotenv": "^8.2.0",
"express": "^4.17.1"
}
}
Deploy your webhook
Add the webhook URL in the Dialogflow CX Console
Use the webhook in Dialogflow CX Page wherein you will need to set the response for BMI output:
Here's the result:
I am trying to add pagination to my Zapier trigger.
The API I am using for the trigger supports pagination, but not using a page number in the traditional sense (ie. page 1,2,3,...). Instead, the API response includes a key (ie. "q1w2e3r4") which should be passed as a parameter to the next request to get the next page of results.
From looking at the docs, I can use {{bundle.meta.page}} (which defaults to 0 unless otherwise set).
I am trying to set {{bundle.meta.page}} in the code editor, with an example shown below:
const options = {
url: 'company_xyz.com/api/widgets',
method: 'GET',
...,
params: {
...,
'pagination_key': bundle.meta.page,
}
}
return z.request(options)
.then((response) => {
response.throwForStatus();
const json_response = response.json;
widgets = json_response.widgets
...
bundle.meta.page = json_response["next_pagination_key"]
return widgets;
});
The problem is that when Zapier tries to retrieve the next page, bundle.meta.page will be 1 instead of the value of "next_pagination_key" from the result of the previous request.
There are docs on cursor-based pagination in the CLI docs.
The relevant block is:
const performWithAsync = async (z, bundle) => {
let cursor;
if (bundle.meta.page) {
cursor = await z.cursor.get(); // string | null
}
const response = await z.request(
'https://5ae7ad3547436a00143e104d.mockapi.io/api/recipes',
{
// if cursor is null, it's sent as an empty query
// param and should be ignored by the server
params: { cursor: cursor }
}
);
// we successfully got page 1, should store the cursor in case the user wants page 2
await z.cursor.set(response.nextPage);
return response.items;
};
This should work in the Zapier Visual Builder, but you might need to use the CLI instead. You can export your integration using the zapier convert CLI command (docs).
Current we send one-way SMS via MS Flow and Twilio which works fine. I have been exploring how to handle incoming SMS, so I followed a guide and managed to utilise Sendgrid to forward incoming SMS to my e-mail address which also works.
However, I am looking to have the original sender receive the SMS via e-mail. I can give each staff member their own phone number which would define each individual but I need a way of Twilio or Sendgrid doing a lookup prior to sending the reply e-mail so it knows where to send it i.e.
User 1 = 01234455678,
User 2 = 01234543245,
User 3 = 06546546445,...etc.
I guess I could re-create the same process for each number but it would require lots of Twilio Functions and Variables which doesn't seem like a great way to accomplish this?
Sorry, I a not much of a coder and try to use on-line guides and forums where I can.
Many Thanks,
JP
You can try something like this, hosting the mapping on Twilio Assets as a Private asset but you could also pull this information into Studio via the HTTP Request Widget if you hosted it on an external server (a bit more advanced). In my case I called my file mapping.json which has the format:
[
{
"name": "John Doe",
"phone": "+14075551212",
"email": "jdoe#example.com"
},
{
"name": "Susan Doe",
"phone": "+19545551212",
"email": "sdoe#example.com"
},
{
"name": "Nadia Doe",
"phone": "+14705551212",
"email": "ndoe#example.com"
},
{
"name": "Carl Doe",
"phone": "+18025551212",
"email": "cdoe#example.com"
}
]
Then you would use the Run Function widget and send in 3 key:value pairs (Function Parameters):
From - {{trigger.message.From}}
To - {{trigger.message.To}}
Body - {{trigger.message.Body}}
Your Twilio Function would then consume these parameters and the contents of the private asset to handle the mapping. Make sure to configure your Twilio Functions environment with the Sendgrid NPM package, #sendgrid/mail version 7.0.1 and you configure the two Sendgrid specific environmental variables below with their respective values (accessed via the context object in the JavaScript):
SENDGRID_API_KEY
FROM_EMAIL_ADDRESS
const fs = require('fs');
const sgMail = require('#sendgrid/mail');
exports.handler = function(context, event, callback) {
let from = event.From;
let to = event.To;
let body = event.Body;
let fileName = 'mapping.json';
let file = Runtime.getAssets()[fileName].path;
let text = fs.readFileSync(file);
let mappings = JSON.parse(text);
// Filter array to match to number
let result = mappings.filter(record => record.phone === to);
if (result.length) {
sgMail.setApiKey(context.SENDGRID_API_KEY);
// Define message params
const msg = {
to: result[0].email,
from: context.FROM_EMAIL_ADDRESS,
text: body,
subject: `New SMS from: ${from}`,
};
// Send message
sgMail.send(msg)
.then(response => {
console.log("Success.");
callback();
})
.catch(err => {
console.log("Not Success.");
callback(err);
});
} else {
console.log("** NO MATCH **");
callback();
}
};
Let me know how it goes.
We are creating a Zapier app to expose our APIs to the public, so anyone can use it. The main endpoint that people are using returns a very large and complex JSON object. Zapier, it looks like, has a really difficult time parsing nested complex JSON. But it does wonderful with a very simple response object such as
{ "field": "value" }
Our data that is being returned has this structure and we want to move some of the fields to the root of the response so it's easily parsed by Zapier.
"networkSections": [
{
"identifier": "Deductible",
"label": "Deductible",
"inNetworkParameters": [
{
"key": "Annual",
"value": " 600.00",
"message": null,
"otherInfo": null
},
{
"key": "Remaining",
"value": " 600.00",
"message": null,
"otherInfo": null
}
],
"outNetworkParameters": null
},
So, can we do something to return for example the remaining deductible?
I got this far (adding outputFields) but this returns an array of values. I'm not sure how to parse through this array either in the Zap or in the App.
{key: 'networkSections[]inNetworkParameters[]key', label: 'xNetworkSectionsKey',type: 'string'},
ie this returns an array of "Annual", "Remaining", etc
Great question. In this case, there's a lot going on, and outputFields can't quite handle it all. :(
In your example, inNetworkParameters contains an array of objects. Throughout our documentation, we refer to these as line items. These lines items can be passed to other actions, but the different expected structures presents a bit of a problem. The way we've handled this is by letting users map line-items from one step's output to another step's input per field. So if step 1 returns
{
"some_array": [
{
"some_key": "some_value"
}
]
}
and the next step needs to send
{
"data": [
{
"some_other_key": "some_value"
}
]
}
users can accomplish that by mapping some_array.some_key to data.some_other_key.
All of that being said, if you want to always return a Remaining Deductible object, you'll have to do it by modifying the result object itself. As long as this data is always in that same order, you can do something akin to
var data = z.JSON.parse(bundle.response.content);
data["Remaining Deductible"] = data.networkSections[0].inNetworkParameters[1].value;
return data;
If the order differs, you'll have to implement some sort of search to find the objects you'd like to return.
I hope that all helps!
Caleb got me where I wanted to go. For completeness this is the solution.
In the creates directory I have a js file for the actual call. The perform part is below.
perform: (z, bundle) => {
const promise = z.request({
url: 'https://api.example.com/API/Example/' + bundle.inputData.elgRequestID,
method: 'GET',
headers: {
'content-type': 'application/json',
}
});
return promise.then(function(result) {
var data = JSON.parse(result.content);
for (var i=0; i<data.networkSections.length; i++) {
for (var j=0; j<data.networkSections[i].inNetworkParameters.length; j++) {
// DEDUCT
if (data.networkSections[i].identifier == "Deductible" &&
data.networkSections[i].inNetworkParameters[j].key == "Annual")
data["zAnnual Deductible"] = data.networkSections[i].inNetworkParameters[j].value;
} // inner for
} // outer for
return data;
});
I'm trying to create a Java Script Code Action on Zapier to fetch Klout Scores for any given Twitter user name...
I've realized that this needs to be done in 2 stages:
1) First get the Klout ID for any Twitter screen_name:
http://api.klout.com/v2/identity.json/twitter?screenName="+screen_name+"&key="+klout_apikey"
Klout replies back to that with JSon:
{"id":"85568398087870011","network":"ks"}
2) second get the Klout score for that Klout id:
http://api.klout.com/v2/user.json/"+klout.id+"/score?key="+klout_apikey"
Klout replies back to this with JSon:
{"score":65.68382904221806,"scoreDelta":{"dayChange":-0.03663891859041257,"weekChange":-0.5495711661078815,"monthChange":-1.4045672671990417},"bucket":"60-69"}
Of course, what I need is the "score":65.68382904221806 object of the JSon reply array.
I use these following JS functions proposed by #KayCee:
var klout_apikey = '<my klout api key>';
fetch("http://api.klout.com/v2/identity.json/twitter?screenName="+screen_name+"&key="+klout_apikey")
.then(function(res) {
return res.json();
})
.then(function(klout) {
console.log(klout);
if(klout.id) {
return fetch("http://api.klout.com/v2/user.json/"+klout.id+"/score?key="+klout_apikey")
}
}).then(function(res) {
return res.json();
}).then(function(body) {
// console.log(body.score);
//Here is where you are telling Zapier what you want to output.
callback(null, body.score)
}).catch(callback); //Required by Zapier for all asynchronous functions.
In the "input data" section of the Zapier code action i pass the screen_name as a variable:
screen_name: [the twitter handle]
What I get back is the following error message:
SyntaxError: Invalid or unexpected token
What is the error that you see? You could do this by simply using the fetch client. You might want to remove the variable declarations before adding this to the code step.
var inputData = {'screen_name': 'jtimberlake'}
//Remove the line above before pasting in the Code step. You will need to configure it in the Zap.
var klout_apikey = '2gm5rt3hsdsdrzgvnskmgm'; //Not a real key
fetch("http://api.klout.com/v2/identity.json/twitter?screenName="+inputData.screen_name+"&key="+klout_apikey)
.then(function(res) {
return res.json();
})
.then(function(body) {
console.log(body);
if(body.id) {
return fetch("http://api.klout.com/v2/user.json/"+body.id+"/score?key="+klout_apikey)
}
}).then(function(res) {
return res.json();
}).then(function(body) {
console.log(body);
//Here is where you are telling Zapier what you want to output.
callback(null, body)
}).catch(callback); //Required by Zapier for all asynchronous functions.
Refer to their documentation here - https://zapier.com/help/code/#introductory-http-example
Also refer to their Store client which allows you to store values (for cache) - https://zapier.com/help/code/#storeclient-javascript