How to call external API with parameters from the Twilio function - axios

I am trying to write one Twilio function. Inside the Twilio function, I have one external API call with parameters for one of my API. API will return the result and that result I am passing using "Say" tag.
I have one Twilio Autopilot Bot in which I have one task added. On execution of that task it will call "Action URL" which is the Twilio function URL.
But somehow this full flow is not working. I think my external API call using AXIOS is not proper. Can you help me to call external API with parameters from the Twilio function? For my Twilio function check following code
Thanks in advance
exports.handler = function(context, event, callback) {
const responseData = await axios.post(`API_PATH/GetMessageData`,'Minute':'3000','MessageType':'2,3'});
const resultData = responseData.data;
let response = {};
response.actions = [];
response.actions.push({"say":"hello sam" + resultData})
callback(null,response)
};

You are calling axios using await, but you have not declared the function to be an async function.
You should also ensure to catch any potential API or connectivity errors when making external requests. That will also show if there's an error in calling your API.
exports.handler = async function(context, event, callback) {
try {
const responseData = await axios.post(`API_PATH/GetMessageData`,'Minute':'3000','MessageType':'2,3'});
const resultData = responseData.data;
let response = {};
response.actions = [];
response.actions.push({"say":"hello sam" + resultData})
callback(null,response)
} catch(error) {
console.error(error)
let response = {};
response.actions = [];
response.actions.push({"say":"There was a problem with the bot, please try again."});
callback(null, response);
}
};
Edit
You also added that your result looks like this:
{
"Message": {
"Text": "Data retrieved successfully"
},
"MessageList": [
{ "ContactNo": "+91xxxxxxxx", "Message": "test message", },
{ "ContactNo": "+91xxxxxxxx", "Message": "test message 2", }
]
}
I'm not sure exactly which of those messages are what you want to send back to the user. Perhaps you want to send both of the Messages inside the MessageList object? You could do that like so (just showing the part of the code inside the try):
const responseData = await axios.post(`API_PATH/GetMessageData`,'Minute':'3000','MessageType':'2,3'});
const resultData = responseData.data;
let response = {};
response.actions = [];
resultData.MessageList.forEach((message) => {
response.actions.push({"say": message.Message });
}
callback(null,response)
This loops through the MessageList and adds a new say action to the response with each message.

Related

Google Action Webhook Inline Editor Returns Before the API call

This is my first Google Action project. I have a simple slot after the invocation. User enters the value on prompt and slot invokes the webhook and make a call to API using the user input. All works fine. However the webhook returns to users even before the API call finish processing and returns the value (line 1 conv.add). I do see in the logs that everything from API is logged fine after the webhook returns to user. Below is the code I am using. I am using inline editor. What am I missing? Thanks for help in advance.
const { conversation } = require('#assistant/conversation');
const functions = require('firebase-functions');
var https = require('https');
const fetch = require('node-fetch');
const app = conversation({debug: true});
app.handle('SearchData', conv => {
const body = JSON.stringify({
val: "this is my body"
});
// prepare the header
var postheaders = {
'Content-Type' : 'application/json',
'Auth' : 'MyAuthCreds'
};
fetch('https://host.domain.com/data', {
method: 'post',
body: body,
headers: postheaders,
})
.then(res => res.json())
.then(d => {
console.log(d);
var profile = d;//JSON.parse(d);
console.log(d.entries);
console.log("Length: "+ d.entries.length);
if(d.entries.length > 0)
{
console.log("Data found");
conv.add("Data found"); //line 1
}
else
{
console.log("no data found");
conv.add("no data found"); //line 1
}
})
.catch(function (err) {
// POST failed...
console.log(err);
});
});
exports.ActionsOnGoogleFulfillment = functions.https.onRequest(app);
Your issue is that your handler is making API calls which are asynchronous, but the Assistant Conversation library doesn't know that you're doing so. So as soon as the handler finishes, it tries to send back a response, but your asynchronous responses (the stuff in the then() blocks) haven't executed yet.
To address this, you need to return a Promise object so the library knows to wait till the Promise is fulfilled before it returns.
Fortunately, in your case, this should be pretty straightforward. fetch and all the .then() blocks return a Promise. So all you need to do is add a return statement in front of the call to fetch. So something like this:
return fetch('https://host.domain.com/data', {

Flutter Cloud Messaging: how to send notification from the app (not from firebase console)

Is it possible to send the notification from within the app instead of a cloud function on firebase?
The reason is, I want to do something similar to: FCM Push Notifications for Flutter, where they have this function that will be deployed to firebase:
export const sendToTopic = functions.firestore
.document('puppies/{puppyId}')
.onCreate(async snapshot => {
const puppy = snapshot.data();
const payload: admin.messaging.MessagingPayload = {
notification: {
title: 'New Puppy!',
body: `${puppy.name} is ready for adoption`,
icon: 'your-icon-url',
click_action: 'FLUTTER_NOTIFICATION_CLICK' // required only for onResume or onLaunch callbacks
}
};
return fcm.sendToTopic('puppies', payload);
});
this method works as intended on firebase cloud functions, however I need the path
.document('puppies/{puppyId}')
to be dynamic depending on which chatroom a user is in, so he would get a notification everytime i new message is send, so the 'chatroom22' would be a variable:
.document('chatroom22/{roomId}')
So is it possible to do this in the app-code, or can this be done in the deployed function?
In response to Doug Stevensons answer
Okay, that makes sence, and works. However, now everybody get the notifications. I want only the people in a given chatroom to receive the notification. I've tried something like this, where the users device token is saved for a given chat-room, then I want to notiffy all those tokens:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().functions);
var newData;
exports.myTrigger = functions.firestore.document('messages/{messageId}/room/{roomId}/message/{messageId2}').onCreate(async (snapshot, context) => {
//
if (snapshot.empty) {
console.log('No Devices');
return;
}
newData = snapshot.data();
const deviceIdTokens = await admin
.firestore()
.collection('messages/{messageId}/room/{roomId}/tokens/{tokenId}')
.get();
var tokens = [];
for (var token of deviceIdTokens.docs) {
tokens.push(token.data().device_token);
}
var payload = {
notification: {
title: `${newData.sender}`,
body: `${newData.message}`,
sound: 'default',
},
data: {
push_key: 'Push Key Value',
key1: newData.message,
},
};
try {
const response = await admin.messaging().sendToDevice(tokens, payload);
console.log('Notification sent successfully');
} catch (err) {
console.log(err);
}
});
But it doesnt seem to work with wildcards.. how can I get the specific destination for each chatroom?
You can use a wildcard for the collection in the path:
functions.firestore.document('{collectionId}/{documentId}')
But this will trigger for all documents in all top-level collecitons, which is probably not what you want.
In fact, using variable names for top-level collections is actually not the preferred way to model data in Firestore. Consider instead having a top-level collection for all rooms, then use subcollections to contain their messages. If you do that, then you function becomes more more clearly defined as:
functions.firestore.document('rooms/{roomId}/messages/{messageId}')
Cloud Fuctions only allows wildcards for full path segments like this. There are no other patterns or regular expressions.

display single record by id with vue js and axios

I have a mongodb express vue js app that displays a list of items in cards which are links to a detail view of each record. If I hover over the card the correct id for the link displays but click any card and it goes to the first document from mongo and the record does not display. The view retrieves an item but always the first one.
How to display a record of the ID of item clicked?
Report.vue
the backend request which works in postman is
// Get Simgle Report
router.get('/:id', async (req, res) => {
const reports = await loadReportsCollection()
await reports.findOne({_id: new mongodb.ObjectID( req.params.id)})
res.send(await reports.find({}).limit(1).toArray())
res.status(200).send()
}
)
ReportService.js looks like
//Find Single Report
static getReport(id) {
return axios.get(`${url}${id}`)
}
and the Report.vue file looks like
mounted () {
this.getReport()
},
methods: {
async getReport() {
try {
const response = await ReportService.getReport(this.$route.params.id)
this.report = response.data
} catch(err) {
this.err = err.message
}
},
}
many thanks for help!
It would seem you are trying to access a param in your api without passing one in your request. You ask for params here:
await reports.findOne({_id: new mongodb.ObjectID( req.params.id)})
but haven't passed any in your request. This should do it:
return axios.get('/:id', {
params: {
id: `${id}`
}
})
To not only get the first entry, but the one you are looking for you need to change your send() parameter.
Here is the working code:
// Get Simgle Report
router.get('/:id', async (req, res) => {
const reports = await loadReportsCollection()
const report = await reports.findOne({_id: new mongodb.ObjectID(req.params.id)})
res.send(await report)
res.status(200).send()
}
)
And as Andrew1325 stated you need to change your axios.get() call also to pass the correct params to it.

What is correct way to respond from webhook running nodejs?

Trying to implement web-hook (with V2 dialogflow) running nodejs. Received response "MalformedResponse 'final_response' must be set.". Below is the code. To the end of POST (app.post) code block was expecting conv.close would send SimpleResponse. But that's not happening. Need help understand why this error is seen and probable direction to solve it.
Thanks
const express = require('express');
const {
dialogflow,
Image,
SimpleResponse,
} = require('actions-on-google')
const bodyParser = require('body-parser');
const request = require('request');
const https = require("https");
const app = express();
const Map = require('es6-map');
// Pretty JSON output for logs
const prettyjson = require('prettyjson');
const toSentence = require('underscore.string/toSentence');
app.use(bodyParser.json({type: 'application/json'}));
// http://expressjs.com/en/starter/static-files.html
app.use(express.static('public'));
// http://expressjs.com/en/starter/basic-routing.html
app.get("/", function (request, response) {
console.log("Received GET request..!!");
//response.sendFile(__dirname + '/views/index.html');
response.end("Response from my server..!!");
});
// Handle webhook requests
app.post('/', function(req, res, next) {
console.log("Received POST request..!!");
// Log the request headers and body, to aide in debugging. You'll be able to view the
// webhook requests coming from API.AI by clicking the Logs button the sidebar.
console.log('======Req HEADERS================================================');
logObject('Request headers: ', req.headers);
console.log('======Req BODY================================================');
logObject('Request body: ', req.body);
console.log('======Req END================================================');
// Instantiate a new API.AI assistant object.
const assistant = dialogflow({request: req, response: res});
// Declare constants for your action and parameter names
//const PRICE_ACTION = 'price'; // The action name from the API.AI intent
const PRICE_ACTION = 'revenue'; // The action name from the API.AI intent
var price = 0.0
// Create functions to handle intents here
function getPrice(assistant) {
console.log('** Handling action: ' + PRICE_ACTION);
let requestURL = 'https://blockchain.info/q/24hrprice';
request(requestURL, function(error, response) {
if(error) {
console.log("got an error: " + error);
next(error);
} else {
price = response.body;
logObject('the current bitcoin price: ' , price);
// Respond to the user with the current temperature.
//assistant.tell("The demo price is " + price);
}
});
}
getPrice(assistant);
var reponseText = 'The demo price is ' + price;
// Leave conversation with SimpleResponse
assistant.intent(PRICE_ACTION, conv => {
conv.close(new SimpleResponse({
speech: responseText,
displayText: responseText,
}));
});
}); //End of app.post
// Handle errors.
app.use(function (err, req, res, next) {
console.error(err.stack);
res.status(500).send('Oppss... could not check the price');
})
// Pretty print objects for logging.
function logObject(message, object, options) {
console.log(message);
console.log(prettyjson.render(object, options));
}
// Listen for requests.
let server = app.listen(process.env.PORT || 3000, function () {
console.log('Your app is listening on ' + JSON.stringify(server.address()));
});
In general, The "final_response" must be set error is because you didn't send anything back. You have a lot going on in your code, and while you're on the right track, there are a few things in the code that could be causing this error.
First - in the code, it looks like you are confused about how to send a response. You have both a call to conv.close() and the commented out assistant.tell(). The conv.close() or conv.ask() methods are the way to send a reply using this version of the library. The tell() method was used by a previous version and is no longer supported.
Next, your code looks like it is only setting up the assistant object when the routing function is called. While this can be done, it is not the usual way to do it. Typically you'll create the assistant object and setup the Intent handlers (using assistant.intent()) as part of the program initialization. This is a rough equivalent to setting up the express app and the routes for it before the request itself comes in.
The portion that sets up the Assistant and then hooks it into a route might look something like this:
const assistant = dialogflow();
app.post('/', assistant);
If you really wanted to examine the request and response objects first, you might do this as something like
const assistant = dialogflow();
app.post('/', function( req, res ){
console.log(JSON.stringify(req.body,null,1));
assistant( req, res );
});
Related to this appears to be that you're trying to execute code in the route handler and then trying to call the intent handler. Again, this might be possible, but isn't the suggested way to use the library. (And I haven't tried to debug your code to see if there are problems in how you're doing it to see if you're doing it validly.) More typical would be to call getPrice() from inside the Intent handler instead of trying to call it from inside the route handler.
But this leads to another problem. The getPrice() function calls request(), which is an asynchronous call. Async calls are one of the biggest problems that causes an empty response. If you are using an async call, you must return a Promise. The easiest way to use a Promise with request() is to use the request-promise-native package instead.
So that block of code might look something (very roughly) like this:
const rp = require('request-promise-native');
function getPrice(){
return rp.get(url)
.then( body => {
// In this case, the body is the value we want, so we'll just return it.
// But normally we have to get some part of the body returned
return body;
});
}
assistant.intent(PRICE_ACTION, conv => {
return getPrice()
.then( price => {
let msg = `The price is ${price}`;
conv.close( new SimpleResponse({
speech: msg,
displayText: msg
});
});
});
The important thing to note about both getPrice() and the intent handler are that they both return a Promise.
Finally, there are some odd aspects in your code. Lines such as res.status(500).send('Oppss... could not check the price'); probably won't do what you think they will do. It won't, for example, send a message to be spoken. Instead, the Assistant will just close the connection and say that something went wrong.
Many thanks to #Prisoner. Below is the V2 working solution based on above comments. Same has been verified on nodejs webhook (without firebase). V1 version of the code was referenced from https://glitch.com/~aog-template-1
Happy coding..!!
// init project pkgs
const express = require('express');
const rp = require('request-promise-native');
const {
dialogflow,
Image,
SimpleResponse,
} = require('actions-on-google')
const bodyParser = require('body-parser');
const request = require('request');
const app = express().use(bodyParser.json());
// Instantiate a new API.AI assistant object.
const assistant = dialogflow();
// Handle webhook requests
app.post('/', function(req, res, next) {
console.log("Received POST request..!!");
console.log('======Req HEADERS============================================');
console.log('Request headers: ', req.headers);
console.log('======Req BODY===============================================');
console.log('Request body: ', req.body);
console.log('======Req END================================================');
assistant(req, res);
});
// Declare constants for your action and parameter names
const PRICE_ACTION = 'revenue'; // The action name from the API.AI intent
var price = 0.0
// Invoke http request to obtain blockchain price
function getPrice(){
console.log('getPrice is invoked');
var url = 'https://blockchain.info/q/24hrprice';
return rp.get(url)
.then( body => {
// In this case, the body is the value we want, so we'll just return it.
// But normally we have to get some part of the body returned
console.log('The demo price is ' + body);
return body;
});
}
// Handle AoG assistant intent
assistant.intent(PRICE_ACTION, conv => {
console.log('intent is triggered');
return getPrice()
.then(price => {
let msg = 'The demo price is ' + price;
conv.close( new SimpleResponse({
speech: msg,
}));
});
});
// Listen for requests.
let server = app.listen(process.env.PORT || 3000, function () {
console.log('Your app is listening on ' + JSON.stringify(server.address()));
});

FaceBook API: Get the Request Object for a request Id - logged into the account that sent the request. Using the "Requests Dialog" API

I am using the "Requests Dialog" to create Facebook requests. Inorder to get the user that the requests were sent to I need to access the Request object using the graph API. I have tried most of the permissions settings that seemed appropriate (read_requests and user_about_me) to get the request object, but instead I get a false in the response. Am I using the wrong permissions?
I am able to access the request object using the graph API from the account that the request was sent to.
http://developers.facebook.com/docs/reference/dialogs/requests/
Return Data - A comma-separated list
of the request_ids that were created.
To learn who the requests were sent
to, you should loop through the
information for each request object
identified by a request id.
I've been asking myself this question a while ago:
How to retrieve all the requests sent by me?
The answer: you can't!
You have two options:
Store the request_id returned when the user sent the request, so you can later access them and get the data you need
Knowing the receiver!
Proof of the above, you can check the friend_request table. The indexable field is the uid_to field!
This is if you want it in know Iframe mode as you don't need Iframe mode any more
function sendRequest() {
FB.ui({
method: 'apprequests',
title: 'Invite friends to join you',
message: 'Come play with me.'
},
function (res) {
if (res && res.request_ids) {
var requests = res.request_ids.join(',');
$.post('FBRequest.ashx',
{ request_ids: requests },
function (resp) { });
}
});
return false;
}
If you want to find out the user ids of the people you just sent a request to. Then this code is what you need:
var request = {
message: msg,
method: 'apprequests',
title: 'Select some of your friends'
};
FB.ui(request, function (data) {
if (data && data.request_ids) {
// Get the uid of the person(s) who was/were invited
var uids = new Array();
FB.api("/?ids=" + data.request_ids.join(), function(data2) {
for (i = 0; i<data.request_ids.length; i++) {
uids[i] = data2[data.request_ids[i]]['to']['id'];
}
# do something with uids here
});
}
});
Don't know if this helps, but here's how I handle it.
Javascript:
function sendRequest() {
FB.ui({
display: 'iframe',
method: 'apprequests',
title: 'Invite friends to join you',
message: 'Come play with me.'
},
function (res) {
if (res && res.request_ids) {
var requests = res.request_ids.join(',');
$.post('FBRequest.ashx',
{ request_ids: requests },
function (resp) { });
}
});
return false;
}
Server side (FBRequest.ashx):
// get operation and data
var ids = HttpContext.Current.Request["request_ids"];
// if we have data
if(ids != null) {
// make batch graph request for request details
var requestIds = ids.Split(',').Select(i => long.Parse(i)).ToList();
var fbApp = new FacebookWebClient([AppId],[AppSecret]);
dynamic parameters = new ExpandoObject();
parameters.ids = ids;
dynamic requests = fbApp.Get(parameters);
// cycle through graph results and do stuff
dynamic req = null;
for(int i=0;i<requestIds.Count;i++) {
try {
req = requests[requestIds[i].ToString()];
// do stuff with request, save to DB, etc.
} catch (Exception ex) {
// error in finding request, continue...
}
}
}
You can access the list of user id's as part of the return data
FB.ui({
method: 'apprequest',
message: 'Use this thing',
}, function(result){
//a list of ids are in here
result.to;
});