Make http request call using Inline Editor in DialogFlow - node.js

Below is the function i am calling but every time i am getting below error
Error - MalformedResponse
Failed to parse Dialogflow response into AppResponse because of empty speech response.
$ below is the complete code
function callExternalAPI () {
return new Promise((resolve, reject) => {
let path = 'path';
console.log('API Request: ' + path);
http.get({host: host, path: path}, (res) => {
let body = '';
res.on('data', (d) => { body += d; });
res.on('end', () => {
let response = JSON.parse(body);
let output = 'response';
console.log(output);
resolve(output);
});
res.on('error', (error) => {
console.log(`Error calling the weather API: ${error}`);
reject();
});
});
let intentMap = new Map();
intentMap.set('CardView',callExternalAPI);
agent.handleRequest(intentMap);
});

The inline editor uses Cloud Functions for Firebase. By default, your project is using the Firebase "Spark" plan, which limits network connections to Google services only.
Since the connection is being rejected, the if (err) block is being triggered, and you have not specified a response to be sent back to the user when this happens, so you're getting the "empty speech response" error.
The easiest solution is to upgrade to paid plan, such as the "Blaze" plan, which will require you to register a credit card, but which has a free tier, so you won't be charged for a base level of operations, which generally covers your development and testing, and may even cover light production usage.
You should likely also setup a response in the event of an error.

Related

YouTube PlaylistItems API causing syntax errors when using JSON.parse()

I have 2 calls to the YouTube v3 API in my NodeJS code: channels and PlaylistItems. They both return JSON and the response to the first call is parsed just fine, but parsing the response to the second call causes a syntax error. I am uncertain whether it's an error on my side or in the PlaylistItems API endpoint.
Here is my code (taken out irrelevant parts):
// At start of the bot, fetches the latest video which is compared to if an announcement needs to be sent
function setLatestVideo () {
fetchData().then((videoInfo) => {
if (videoInfo.error) return;
latestVideo = videoInfo.items[0].snippet.resourceId.videoId;
});
}
// Fetches data required to check if there is a new video release
async function fetchData () {
let path = `channels?part=contentDetails&id=${config.youtube.channel}&key=${config.youtube.APIkey}`;
const channelContent = await callAPI(path);
path = `playlistItems?part=snippet&maxResults=1&playlistId=${channelContent.items[0].contentDetails.relatedPlaylists.uploads}&key=${config.youtube.APIkey}`;
const videoInfo = await callAPI(path);
return videoInfo;
}
// Template HTTPS get function that interacts with the YouTube API, wrapped in a Promise
function callAPI (path) {
return new Promise((resolve) => {
const options = {
host: 'www.googleapis.com',
path: `/youtube/v3/${path}`
};
https.get(options, (res) => {
if (res.statusCode !== 200) return;
const rawData = [];
res.on('data', (chunk) => rawData.push(chunk));
res.on('end', () => {
try {
resolve(JSON.parse(rawData));
} catch (error) { console.error(`An error occurred parsing the YouTube API response to JSON, ${error}`); }
});
}).on('error', (error) => console.error(`Error occurred while polling YouTube API, ${error}`));
});
}
Examples of errors I'm getting: Unexpected token , in JSON and Unexpected number in JSON
Till ~2 weeks ago this code used to work just fine without throwing any errors, I have no clue what has changed and can't seem to figure it out. What could possibly be causing this?
10 minutes later I figure out the fix! The variable rawData holds a Buffer and looking more into that, I figured that I should probably use Buffer.concat() on rawData before calling JSON.parse() on it. Turns out, that's exactly what was needed.
I'm still unsure how this was causing problems only 6 months after writing this code, but that tends to happen.
Changed code:
res.on('end', () => {
try {
resolve(JSON.parse(Buffer.concat(rawData)));
} catch (error) { console.error(`An error occurred parsing the YouTube API response to JSON, ${error}`); }
});

Keep variable between promises in a function?

On firebase function I need to get data from Paypal and do 4 things :
1. returns an empty HTTP 200 to them.
2. send the complete message back to PayPal using `HTTPS POST`.
3. get back "VERIFIED" message from Paypal.
4. *** write something to my Firebase database only here.
What I do now works but i am having a problem with (4).
exports.contentServer = functions.https.onRequest((request, response) => {
....
let options = {
method: 'POST',
uri: "https://ipnpb.sandbox.paypal.com/cgi-bin/webscr",
body: verificationBody
};
// ** say 200 to paypal
response.status(200).end();
// ** send POST to paypal back using npm request-promise
return rp(options).then(body => {
if (body === "VERIFIED") {
//*** problem is here!
return admin.firestore().collection('Users').add({request.body}).then(writeResult => {return console.log("Request completed");});
}
return console.log("Request completed");
})
.catch(error => {
return console.log(error);
})
As you can see when I get final VERIFIED from Paypal I try to write to the db with admin.firestore().collection('Users')..
I get a warning on compile :
Avoid nesting promises
for the write line.
How and where should I put this write at that stage of the promise ?
I understand that this HTTPS Cloud Function is called from Paypal.
By doing response.status(200).end(); at the beginning of your HTTP Cloud Function you are terminating it, as explained in the doc:
Important: Make sure that all HTTP functions terminate properly. By
terminating functions correctly, you can avoid excessive charges from
functions that run for too long. Terminate HTTP functions with
res.redirect(), res.send(), or res.end().
This means that in most cases the rest of the code will not be executed at all or the function will be terminated in the middle of the asynchronous work (i.e. the rp() or the add() methods)
You should send the response to the caller only when all the asynchronous work is finished. The following should work:
exports.contentServer = functions.https.onRequest((request, response) => {
let options = {
method: 'POST',
uri: "https://ipnpb.sandbox.paypal.com/cgi-bin/webscr",
body: verificationBody
};
// ** send POST to paypal back using npm request-promise
return rp(options)
.then(body => {
if (body === "VERIFIED") {
//*** problem is here!
return admin.firestore().collection('Users').add({ body: request.body });
} else {
console.log("Body is not verified");
throw new Error("Body is not verified");
}
})
.then(docReference => {
console.log("Request completed");
response.send({ result: 'ok' }); //Or any other object, or empty
})
.catch(error => {
console.log(error);
response.status(500).send(error);
});
});
I would suggest you watch the official Video Series on Cloud Functions from Doug Stevenson (https://firebase.google.com/docs/functions/video-series/) and in particular the first video on Promises titled "Learn JavaScript Promises (Pt.1) with HTTP Triggers in Cloud Functions".

Error while deploying dialog flow sample code

I am trying to deploy sample weather dialog flow chat bot,
I am deploying it as per instruction given in dialog flow manual
for deploying index.js I am using following command
gcloud beta functions deploy helloHttp --stage-bucket weather-example --trigger-http
after deploying file I get following error
ERROR: (gcloud.beta.functions.deploy) OperationError: code=3, message=Function load error: Node.js module defined by file index.js is expected to export function named helloHttp
I dont know how to resolved it, I am new to google cloud and dialog flow,
here is my index.js file I just added my whether api key
'use strict';
const http = require('http');
const host = 'api.worldweatheronline.com';
const wwoApiKey = 'MY wheather key';
exports.weatherWebhook = (req, res) => {
// Get the city and date from the request
let city = req.body.queryResult.parameters['geo-city']; // city is a required param
// Get the date for the weather forecast (if present)
let date = '';
if (req.body.queryResult.parameters['date']) {
date = req.body.queryResult.parameters['date'];
console.log('Date: ' + date);
}
// Call the weather API
callWeatherApi(city, date).then((output) => {
res.json({ 'fulfillmentText': output }); // Return the results of the weather API to Dialogflow
}).catch(() => {
res.json({ 'fulfillmentText': `I don't know the weather but I hope it's good!` });
});
};
function callWeatherApi (city, date) {
return new Promise((resolve, reject) => {
// Create the path for the HTTP request to get the weather
let path = '/premium/v1/weather.ashx?format=json&num_of_days=1' +
'&q=' + encodeURIComponent(city) + '&key=' + wwoApiKey + '&date=' + date;
console.log('API Request: ' + host + path);
// Make the HTTP request to get the weather
http.get({host: host, path: path}, (res) => {
let body = ''; // var to store the response chunks
res.on('data', (d) => { body += d; }); // store each response chunk
res.on('end', () => {
// After all the data has been received parse the JSON for desired data
let response = JSON.parse(body);
let forecast = response['data']['weather'][0];
let location = response['data']['request'][0];
let conditions = response['data']['current_condition'][0];
let currentConditions = conditions['weatherDesc'][0]['value'];
// Create response
let output = `Current conditions in the ${location['type']}
${location['query']} are ${currentConditions} with a projected high of
${forecast['maxtempC']}°C or ${forecast['maxtempF']}°F and a low of
${forecast['mintempC']}°C or ${forecast['mintempF']}°F on
${forecast['date']}.`;
// Resolve the promise with the output text
console.log(output);
resolve(output);
});
res.on('error', (error) => {
console.log(`Error calling the weather API: ${error}`)
reject();
});
});
});
}
can you please tell me whats going wrong here?
As per you index.js file, you are exporting weatherWebhook named javascript function, so you should either put value of deploy function name attribute of deploy command of GCloud beta functions same as exported method name because by default when a Google Cloud Function is triggered, it executes a JavaScript function with the same name or if it cannot find a function with the same name, it executes a function named function.
For this your command would be, try this
gcloud beta functions deploy weatherWebhook --stage-bucket weather-example --trigger-http
OR
you can use --entry-point argument to tell gcp which function would be the entry point for your Google Cloud Function, if you want different name for your deployment.
gcloud beta functions deploy helloHttp --entry-point weatherWebhook --stage-bucket weather-example --trigger-http
For more info about Google cloud function deploy command, click here
Hello friends I mistakenly using wrong deployment url,
Correct url is a follows:
gcloud beta functions deploy helloHttp --entry-point weatherWebhook --stage-bucket weather-example --trigger-http

Error: getaddrinfo ENOTFOUND in node.js webhook for dialogflow

I am trying to follow the dialogflow tutorial. I have set up a node.js webhook, that is called from Dialogflow, inside the webhook code I call out to an api. However, my node.js webhook is saying "Error: getaddrinfo ENOTFOUND". This works fine when I run it in visual code but cannot find the api with in the nodejs webhook, when called via DialogFlow. There is something about the fact that it is being called from Dialogflow that seems to be making it not work.
I have spent a lot of time on this, and had previsouly discovered that DialogFlow wont work with https where it is a self signed certificate, as such I put an azure function, so the webhook calls the azure function and then the azure function call the api that I need.
Sorry for the long post...
Here is the node.js code:
'use strict';
const http = require('http');
var request = require('request');
const apiUrl ="https://myapi";
exports.saledurationWebhook = (req, res) => {
// Get the city and date from the request
// let city = req.body.queryResult.parameters['geo-city']; // city is a required param
let city = "sdjk";
// Call the weather API
callSalesDurationApi(city).then((output) => {
res.json({ 'fulfillmentText': output }); // Return the results of the weather API to Dialogflow
})
.catch((err) => {
console.log(err);
//res.json({ 'fulfillmentText': `I don't know the sales duration is but I hope it's quick!` });
res.json({ 'fulfillmentText': err.message});
})
;
};
function callSalesDurationApi(city) {
return new Promise((resolve, reject) => {
console.log('API Request: ' + apiUrl);
var myJSONObject = {
"Inputs": "stuff"
};
request({
url: apiUrl,
method: "POST",
json: true, // <--Very important!!!
body: myJSONObject
}, function (error, response, body) {
console.log("successfully called api");
let output = "Current conditions in the " + body;
console.log(output);
console.log(body);
resolve(output);
});
});
}
Does anyone know why this might be happening? Or what frther steps I can take to investigate it? I have already looked at the loges for the webhook, and for the azure function.
Any help would be really gratefully recieved, I have already wasted days on this. If this is a duplicate question then I am sorry, I have tried to look for existing answers on this issue.
Thanks Laura
I have found this question already answered at: https://stackoverflow.com/a/46692487/7654050
It is because I have not set billing up for this project. I thought it been set up as it is on my work account.

How do I call a third party Rest API from Firebase function for Actions on Google

I am trying to call a rest API from Firebase function which servers as a fulfillment for Actions on Google.
I tried the following approach:
const { dialogflow } = require('actions-on-google');
const functions = require('firebase-functions');
const http = require('https');
const host = 'wwws.example.com';
const app = dialogflow({debug: true});
app.intent('my_intent_1', (conv, {param1}) => {
// Call the rate API
callApi(param1).then((output) => {
console.log(output);
conv.close(`I found ${output.length} items!`);
}).catch(() => {
conv.close('Error occurred while trying to get vehicles. Please try again later.');
});
});
function callApi (param1) {
return new Promise((resolve, reject) => {
// Create the path for the HTTP request to get the vehicle
let path = '/api/' + encodeURIComponent(param1);
console.log('API Request: ' + host + path);
// Make the HTTP request to get the vehicle
http.get({host: host, path: path}, (res) => {
let body = ''; // var to store the response chunks
res.on('data', (d) => { body += d; }); // store each response chunk
res.on('end', () => {
// After all the data has been received parse the JSON for desired data
let response = JSON.parse(body);
let output = {};
//copy required response attributes to output here
console.log(response.length.toString());
resolve(output);
});
res.on('error', (error) => {
console.log(`Error calling the API: ${error}`)
reject();
});
}); //http.get
}); //promise
}
exports.myFunction = functions.https.onRequest(app);
This is almost working. API is called and I get the data back. The problem is that without async/await, the function does not wait for the "callApi" to complete, and I get an error from Actions on Google that there was no response. After the error, I can see the console.log outputs in the Firebase log, so everything is working, it is just out of sync.
I tried using async/await but got an error which I think is because Firebase uses old version of node.js which does not support async.
How can I get around this?
Your function callApi returns a promise, but you don't return a promise in your intent handler. You should make sure you add the return so that the handler knows to wait for the response.
app.intent('my_intent_1', (conv, {param1}) => {
// Call the rate API
return callApi(param1).then((output) => {
console.log(output);
conv.close(`I found ${output.length} items!`);
}).catch(() => {
conv.close('Error occurred while trying to get vehicles. Please try again later.');
});
});

Resources