How do I specify which API data I want to use? - javascript

I am developing an Alexa Skill (using v2 and javascript) and I'm attempting to make an API GET call to the USDA API.
At this point I've copy/pasted from here and I'm using the USDA API example from here (with some minor changes). as an attempt just to get the connection to work and return anything that proves it worked.
The error I currently receive is: Error handled: TypeError: Cannot read property 'generalSearchInput' of undefined at Object.handle (/var/task/index.js:39:32)
at process._tickCallback (internal/process/next_tick.js:68:7)
Directly before the error I get the return of: every entry about Cottage Cheese that is in the USDA API, I.E. WAY too much from the console.log(response). I just want the first one, or even just the name.
Console logs have told me that I am receiving data back from the call, so I know it is working. My question is: How do I set the speechOutput to the specific piece of information I need, rather than the entire API object that is returned?
The speechOutput that I'm looking for should say: Cottage Cheese
The response that I am receiving is: Output from Alexa saying:
Sorry, I had trouble doing what you asked. Please try again
index.js
const Alexa = require('ask-sdk-core');
const LaunchRequestHandler = {
canHandle(handlerInput) {
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'LaunchRequest';
},
handle(handlerInput) {
const speakOutput = 'Welcome to food points! What food would you like to know about?';
return handlerInput.responseBuilder
.speak(speakOutput)
.reprompt(speakOutput)
.getResponse();
}
};
const FoodPointsIntentHandler = {
canHandle(handlerInput) {
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
&& Alexa.getIntentName(handlerInput.requestEnvelope) === 'FoodPointsIntent';
},
async handle(handlerInput) {
console.log("THIS.EVENT = " + JSON.stringify(this.event));
var speakOutput = 'Sorry, there was an error';
//var https = require('https');
const { requestEnvelope } = handlerInput;
const userInput = Alexa.getSlotValue(requestEnvelope, 'FoodQuery');
const response = await httpGet();
console.log(response);
/*
const food = userInput;
speakOutput = food;
*/
speakOutput = response.value.generalSearchInput;
return handlerInput.responseBuilder
.speak(speakOutput)
//.reprompt('add a reprompt if you want to keep the session open for the user to respond')
.getResponse();
}
};
const TestIntentHandler = {
canHandle(handlerInput) {
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
&& Alexa.getIntentName(handlerInput.requestEnvelope) === 'TestIntent';
},
handle(handlerInput) {
console.log("THIS.EVENT = " + JSON.stringify(this.event));
var speakOutput = 'Sorry, there was an error';
const { requestEnvelope } = handlerInput;
const userInput = Alexa.getSlotValue(requestEnvelope, 'TestQuery');
const food = userInput;
speakOutput = food;
return handlerInput.responseBuilder
.speak(speakOutput)
//.reprompt('add a reprompt if you want to keep the session open for the user to respond')
.getResponse();
}
};
function httpGet() {
return new Promise(((resolve, reject) => {
var options = {
host: 'api.nal.usda.gov',
port: 443,
path: '/fdc/v1/foods/search?api_key=DEMO_KEY&query=Cheddar%20Cheese',
method: 'GET',
};
var https = require('https');
const request = https.request(options, (response) => {
response.setEncoding('utf8');
let returnData = '';
response.on('data', (chunk) => {
returnData += chunk;
});
response.on('end', () => {
resolve(JSON.parse(returnData));
});
response.on('error', (error) => {
reject(error);
});
});
request.end();
}));
}
/****************************REMEMBER TO UPDATE THIS*************************/
const HelpIntentHandler = {
canHandle(handlerInput) {
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
&& Alexa.getIntentName(handlerInput.requestEnvelope) === 'AMAZON.HelpIntent';
},
handle(handlerInput) {
const speakOutput = 'You can say hello to me! How can I help?';
return handlerInput.responseBuilder
.speak(speakOutput)
.reprompt(speakOutput)
.getResponse();
}
};
const CancelAndStopIntentHandler = {
canHandle(handlerInput) {
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
&& (Alexa.getIntentName(handlerInput.requestEnvelope) === 'AMAZON.CancelIntent'
|| Alexa.getIntentName(handlerInput.requestEnvelope) === 'AMAZON.StopIntent');
},
handle(handlerInput) {
const speakOutput = 'Goodbye!';
return handlerInput.responseBuilder
.speak(speakOutput)
.getResponse();
}
};
const SessionEndedRequestHandler = {
canHandle(handlerInput) {
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'SessionEndedRequest';
},
handle(handlerInput) {
// Any cleanup logic goes here.
return handlerInput.responseBuilder.getResponse();
}
};
// The intent reflector is used for interaction model testing and debugging.
// It will simply repeat the intent the user said. You can create custom handlers
// for your intents by defining them above, then also adding them to the request
// handler chain below.
const IntentReflectorHandler = {
canHandle(handlerInput) {
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest';
},
handle(handlerInput) {
const intentName = Alexa.getIntentName(handlerInput.requestEnvelope);
const speakOutput = `You just triggered ${intentName}`;
return handlerInput.responseBuilder
.speak(speakOutput)
//.reprompt('add a reprompt if you want to keep the session open for the user to respond')
.getResponse();
}
};
// Generic error handling to capture any syntax or routing errors. If you receive an error
// stating the request handler chain is not found, you have not implemented a handler for
// the intent being invoked or included it in the skill builder below.
const ErrorHandler = {
canHandle() {
return true;
},
handle(handlerInput, error) {
console.log(`~~~~ Error handled: ${error.stack}`);
const speakOutput = `Sorry, I had trouble doing what you asked. Please try again.`;
return handlerInput.responseBuilder
.speak(speakOutput)
.reprompt(speakOutput)
.getResponse();
}
};
// The SkillBuilder acts as the entry point for your skill, routing all request and response
// payloads to the handlers above. Make sure any new handlers or interceptors you've
// defined are included below. The order matters - they're processed top to bottom.
exports.handler = Alexa.SkillBuilders.custom()
.addRequestHandlers(
LaunchRequestHandler,
FoodPointsIntentHandler,
TestIntentHandler,
HelpIntentHandler,
CancelAndStopIntentHandler,
SessionEndedRequestHandler,
IntentReflectorHandler, // make sure IntentReflectorHandler is last so it doesn't override your custom intent handlers
)
.addErrorHandlers(
ErrorHandler,
)
.lambda();

I figured it out. Hopefully this can help somebody who may be stuck.
I was calling the API data incorrectly.
The code that did what I needed
var nvArray = [];
for(var i = 0; i < response.foods[0].foodNutrients.length; i++) {
var nvArrayString = [response.foods[0].foodNutrients[i].nutrientName] + '';
var nvShortString = nvArrayString.split(',', 1);
nvArray.push(nvShortString);
}
var nvArraySpeakOutputString = nvArray.join();
The actual piece of code that fixed my errors
response.foods[0].foodNutrients[i].nutrientName
I was looking at the wrong part of the data being given to me, and thus calling something that didn't exist, i.e. response.value.generalSearchInput.
Additional information that may be helpful
nvArray is used so that I can put multiple variables from the data into one place (using push and pop).
This allowed me to split the data into shorter pieces each time the loop runs (used to keep the output from being a wall of text for each item).
nvArraySpeakOutputString is the nvArray joined together, allowing Alexa to speak it all as one variable, preventing me from having to type
varA + '. next ' + varB
and so on for each item.
Anyway, hope this helps somebody at some point!

Related

Problems getting slot in AWS Lambda function

I am trying to build a simple Alexa Skill. Now, I want to access some of my slot values. This is my code. When I uncomment even the line that I define Alexa in, my skill will not work. Also, if I only uncomment the line defining var text, I still get "there was a problem with the skills response". Const gives the same output. I am using custom slots called recipe. How can I access the slots in my lambda function? Thanks.
const breakfast = {
"bacon and eggs":["bacon","egg"],
"buttered toast":["bread", "butter"]
};
const lunch = {
"ham sandwich":["ham","cheese"]
};
const dinner = { "Steak and eggs": ['steak','eggs']};
//const Alexa = require('ask-sdk-core');
exports.handler = (event, context, callback) => {
try {
if (event.request.type === 'LaunchRequest') {
callback(null, buildResponse('Hello from Lambda'));
} else if (event.request.type === 'IntentRequest') {
const intentName = event.request.intent.name;
if (intentName === 'breakfast') {
callback(null, buildResponse(Object.keys(breakfast)+"") );
}
else if (intentName === 'lunch') {
callback(null, buildResponse(Object.keys(lunch)+"") );
}
else if (intentName === 'dinner') {
callback(null, buildResponse(Object.keys(dinner)+"") );
}
else if (intentName ==='requestRecipe'){
//var text = this.event.request.intent.slots.recipe.value;
//const meal = Alexa.getSlotValue(intentName, "meal")
callback(null, buildResponse("Recipe requested") );
}
else {
callback(null, buildResponse("Sorry, i don't understand"));
}
} else if (event.request.type === 'SessionEndedRequest') {
callback(null, buildResponse('Session Ended'));
}
} catch (e) {
context.fail(`Exception: ${e}`);
}
};
function buildResponse(response) {
return {
version: '1.0',
response: {
outputSpeech: {
type: 'PlainText',
text: response,
},
shouldEndSession: false,
},
sessionAttributes: {},
};
}
for a bit of context: my lambda has the endpoint of what my alexa hosted skill was, and the alexa skill has the endpoint of the lambda. when I say const gives the same output, i mean instead of using var, when I use const, it does the same thing. The JSON file that i get as a reply is empty brackets.
I found the issue behind my problem. Instead of using
//var text = this.event.request.intent.slots.recipe.value;
i simply did var text = event.request.intent.slots.recipe.value; I am now able to use text in building a response or something like that.

Alexa Skill: is it possible to have Alexa play a .mp3 from Alexa Developer Console - Alexa Hosted?

I'm trying to create a skill for Alexa that reproduces .mp3 files based on the category of the tg I choose.
I state that I never used node.js and that I looked at a course on how to create alexa skill.
Until now I have created the skeleton of the skill and I have created a database.json file that contains the various categories of news with attached links.
INDEX.JS
const Alexa = require('ask-sdk-core');
const database = require('./database');
function getCategoryStreamUrl(name) {
let category = database.find(e => e.name === name);
if (category !== null && category !== undefined) {
return category.StreamUrl;
} else {
return "";
}
}
function getCategoryUpdateDate(name) {
let category = database.find(e => e.name === name);
if (category !== null && category !== undefined) {
return category.UpdateDate;
} else {
return "";
}
}
const LaunchRequestHandler = {
canHandle(handlerInput) {
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'LaunchRequest';
},
handle(handlerInput) {
const speakOutput = 'Welcome, with this skill you can listen to our TG. You can choose a category or say Help.';
const repromptOutput = 'You can choose a category or say Help. What do you want to do?'
return handlerInput.responseBuilder
.speak(speakOutput)
.reprompt(repromptOutput)
.getResponse();
}
};
const ScegliCategoriaIntentHandler = {
canHandle(handlerInput) {
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
&& Alexa.getIntentName(handlerInput.requestEnvelope) === 'ScegliCategoriaIntent';
},
handle(handlerInput) {
const speakOutput = 'The TGs available are: Political, Lazio, Environment, Health, School, Pediatrics, Rehabilitation, Agriculture, Psychology and Youth. Which do you want to hear?'
return handlerInput.responseBuilder
.speak(speakOutput)
.reprompt(repromptOutput)
.getResponse();
}
};
const CategoriaIntentHandler = {
canHandle(handlerInput) {
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
&& Alexa.getIntentName(handlerInput.requestEnvelope) === 'CategoriaIntent';
},
handle(handlerInput) {
const filledSlots = handlerInput.requestEnvelope.request.intent.slots;
const slotValues = getSlotValues(filledSlots);
console.log("CategoriaIntentHandler >>>>>>>>>>>>>>>>");
console.log(slotValues);
const categoryName = slotValues["category"].synonym
const categoryStreamUrl = getCategoryStreamUrl(categoryName);
const categoryUpdateDate = getCategoryUpdateDate(categoryName);
var speakOutput = "";
if (categoryStreamUrl !== "") {
speakOutput = "You have chosen the TG " + categoryName + ". The TG was updated on " + categoryUpdateDate + ". " + categoryStreamUrl + ". If you want to hear other news, name another TG or you can say STOP to close this skill.";
} else {
speakOutput = "I'm sorry, the selected TG is not available. You can say the name of another TG or 'categories' to hear the list of available categories. What do you want to do?";
}
return handlerInput.responseBuilder
.speak(speakOutput)
.reprompt("If you want to hear more news, say the name of another TG. You can say 'categories' to listen to the list of available categories or you can say STOP to close this skill. What do you want to do?")
.getResponse();
}
};
const HelpIntentHandler = {
canHandle(handlerInput) {
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
&& Alexa.getIntentName(handlerInput.requestEnvelope) === 'AMAZON.HelpIntent';
},
handle(handlerInput) {
const speakOutput = "You can say 'categories' to listen to the list of available categories or you can say STOP to close this skill. What do you want to do?";
const repromptOutput = "If you want to stop listening, say STOP."
return handlerInput.responseBuilder
.speak(speakOutput)
.reprompt(repromptOutput)
.getResponse();
}
};
const CancelAndStopIntentHandler = {
canHandle(handlerInput) {
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
&& (Alexa.getIntentName(handlerInput.requestEnvelope) === 'AMAZON.CancelIntent'
|| Alexa.getIntentName(handlerInput.requestEnvelope) === 'AMAZON.StopIntent');
},
handle(handlerInput) {
const speakOutput = "Goodbye!";
return handlerInput.responseBuilder
.speak(speakOutput)
.getResponse();
}
};
const SessionEndedRequestHandler = {
canHandle(handlerInput) {
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'SessionEndedRequest';
},
handle(handlerInput) {
// Any cleanup logic goes here.
return handlerInput.responseBuilder.getResponse();
}
};
// The intent reflector is used for interaction model testing and debugging.
// It will simply repeat the intent the user said. You can create custom handlers
// for your intents by defining them above, then also adding them to the request
// handler chain below.
const IntentReflectorHandler = {
canHandle(handlerInput) {
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest';
},
handle(handlerInput) {
const intentName = Alexa.getIntentName(handlerInput.requestEnvelope);
const speakOutput = `You just triggered ${intentName}`;
return handlerInput.responseBuilder
.speak(speakOutput)
//.reprompt('add a reprompt if you want to keep the session open for the user to respond')
.getResponse();
}
};
// Generic error handling to capture any syntax or routing errors. If you receive an error
// stating the request handler chain is not found, you have not implemented a handler for
// the intent being invoked or included it in the skill builder below.
const ErrorHandler = {
canHandle() {
return true;
},
handle(handlerInput, error) {
console.log(`~~~~ Error handled: ${error.stack}`);
const speakOutput = `I'm sorry I did not understand. Say 'Help' to know the available commands.`;
return handlerInput.responseBuilder
.speak(speakOutput)
.reprompt(speakOutput)
.getResponse();
}
};
function getSlotValues (filledSlots) {
let slotValues = {}; //
console.log('The filled slots: ' + JSON.stringify(filledSlots));
Object.keys(filledSlots).forEach(function(item) {
var name = filledSlots[item].name;
if(filledSlots[item]&&
filledSlots[item].resolutions &&
filledSlots[item].resolutions.resolutionsPerAuthority[0] &&
filledSlots[item].resolutions.resolutionsPerAuthority[0].status &&
filledSlots[item].resolutions.resolutionsPerAuthority[0].status.code ) {
switch (filledSlots[item].resolutions.resolutionsPerAuthority[0].status.code) {
case "ER_SUCCESS_MATCH":
slotValues[name] = {
"synonym": filledSlots[item].value,
"resolved": filledSlots[item].resolutions.resolutionsPerAuthority[0].values[0].value.name,
"isValidated": true
};
break;
case "ER_SUCCESS_NO_MATCH":
slotValues[name] = {
"synonym": filledSlots[item].value,
"resolved": filledSlots[item].value,
"isValidated":false
};
break;
}
} else {
slotValues[name] = {
"synonym": filledSlots[item].value,
"resolved": filledSlots[item].value,
"isValidated": false
};
}
},this);
return slotValues;
}
// The SkillBuilder acts as the entry point for your skill, routing all request and response
// payloads to the handlers above. Make sure any new handlers or interceptors you've
// defined are included below. The order matters - they're processed top to bottom.
exports.handler = Alexa.SkillBuilders.custom()
.addRequestHandlers(
LaunchRequestHandler,
ScegliCategoriaIntentHandler,
CategoriaIntentHandler,
HelpIntentHandler,
CancelAndStopIntentHandler,
SessionEndedRequestHandler,
IntentReflectorHandler, // make sure IntentReflectorHandler is last so it doesn't override your custom intent handlers
)
.addErrorHandlers(
ErrorHandler,
)
.lambda();
DATABASE.JSON
[
{
"uid":"ID",
"updateDate": "DATA",
"name": "NAME SLOT",
"titleText": "TG 01",
"mainText":"",
"streamUrl":"https://www.mysite.it/tg.mp3",
"redirectionUrl":"https://www.mysite.it/category/tg"
},
{
etc_01
},
{
etc_02
},
{
etc_03
},
]
The problem is that now it doesn't work, in fact when I ask alexa to tell me the TG 01 she tells me:
"You have chosen the TG 01. The TG was updated on undefinied. Undefinied. another TG or you can say STOP to close this skill. "
In short, it does not recognize categoryUpdateDate or categoryStreamUrl.
What do I have to do to fix it?
You are returning the wrong data in both functions:
they should be: category.updateDate and category.streamUrl, because you are reading a JSON file. (maybe read this guide to understand better how to read and access JSON properties and so on..)
function getCategoryStreamUrl(name) {
let category = database.find(e => e.name === name);
if (category !== null && category !== undefined) {
return category.streamUrl;
} else {
return "";
}
}
function getCategoryUpdateDate(name) {
let category = database.find(e => e.name === name);
if (category !== null && category !== undefined) {
return category.updateDate;
} else {
return "";
}
}

API distance matrix Call in Javascript alone

So I am an extremely beginner programmer trying to understand how to call/get data from Google Distance Matrix API in purely Javascript.
Below is my codes
https = require('https')
function getDistance(app){
console.log('testing1');
let defaultPath = 'maps.googleapis.com/maps/api/distancematrix/json?units=metric';
let APIKey = '&key=<API KEY HERE>';
let origin = 'Tampines Avenue 1, Temasek Polytechnic';
let destination = 'Tampines Central 5, Tampines Mall';
let newPath = defaultPath+ '&origins=' + origin + '&destinations=' +destination + APIKey;
console.log('https://'+ newPath); //this prints the correct path
https.get('https://'+ newPath, (res) =>{ //i assume the problem begins here?
console.log('testing2')//doesnt print
let body = ''; //no idea what this does. Copied from a school lab sheet
res.on('data', (d) => {
console.log('testing3') //this doesn't print
let response = JSON.parse(body);
let distance = response.rows[0].elements.distance.text //are these correct?
let travelTime = response.rows[0].elements.duration.text
console.log(distance) //doesnt print
console.log(response.rows[0]) //doesnt print
app.add('distance between is ' + distance + '. Time taken is ' + travelTime);
console.log(response);
});
});
}
I'm pretty sure the 'https://'+ newPath is correct as it is printed in the console.log
And throwing the link into a browser
I get this as result
so can someone please explain to me what i'm doing wrong?
Oh and also, dont know if this is necessary but im doing this in dialogflow.cloud.google.com as a chatbot for my assignment
This is the error I get
Error: No responses defined for platform: undefined at
WebhookClient.send_
(/srv/node_modules/dialogflow-fulfillment/src/dialogflow-fulfillment.js:428:13)
at promise.then
(/srv/node_modules/dialogflow-fulfillment/src/dialogflow-fulfillment.js:246:38)
at at process._tickDomainCallback
(internal/process/next_tick.js:229:7)
I found a similar problem on GitHub: https://github.com/dialogflow/dialogflow-fulfillment-nodejs/issues/22
The solution was
Okay, so here's what I did to make this work properly.
I used request-promise-native instead of http to make the AJAX Call.
const rp = require('request-promise-native');
I returned a promise from the handler of the promise that rp returns.
return rp(options).then(data => { // Extract relevant details from data. // Add it to the agent. agent.add('Here's the data: ', JSON.stringify(data)); return Promise.resolve(agent); });
The full code is
'use strict';
const express = require('express');
const bodyParser = require('body-parser');
const rp = require('request-promise-native');
const { WebhookClient } = require('dialogflow-fulfillment');
const { Card, Suggestion } = require('dialogflow-fulfillment');
const { Carousel } = require('actions-on-google');
process.env.DEBUG = 'dialogflow:debug'; // enables lib debugging statements
const imageUrl = 'https://developers.google.com/actions/images/badges/XPM_BADGING_GoogleAssistant_VER.png';
const imageUrl2 = 'https://lh3.googleusercontent.com/Nu3a6F80WfixUqf_ec_vgXy_c0-0r4VLJRXjVFF_X_CIilEu8B9fT35qyTEj_PEsKw';
const linkUrl = 'https://assistant.google.com/';
const API_KEY = 'YOUR-API-KEY-HERE';
const server = express();
server.use(
bodyParser.urlencoded({
extended: true
})
);
server.use(bodyParser.json());
server.post('/dialog-flow-fulfillment', (request, response) => {
const agent = new WebhookClient({ request, response });
function googleAssistantOther(agent) {
let conv = agent.conv();
conv.ask(`Sure! Details about ${agent.parameters.movie} coming your way!`);
return getMovieDataFromOMDb(agent.parameters.movie).then(movie => {
conv.ask(`Okay! So there you go.`);
conv.ask(new Card({
title: `${movie.Title}(${movie.Year})`,
imageUrl: movie.Poster,
text: `${movie.Rated} | ${movie.Runtime} | ${movie.Genre} | ${movie.Released} (${movie.Country})`,
buttonText: 'Website',
buttonUrl: movie.Website || `https://www.imdb.com/title/${movie.imdbID}`
}));
conv.ask(new Suggestion(`More details`));
conv.ask(new Suggestion(`Another movie`));
agent.add(conv);
return Promise.resolve(agent);
});
}
function welcome(agent) {
agent.add(`Welcome to my agent!`);
}
function fallback(agent) {
agent.add(`I didn't understand`);
agent.add(`I'm sorry, can you try again?`);
}
function getMovieDetailsOther(agent) {
return getMovieDataFromOMDb(agent.parameters.movie).then(movie => {
// const responseDataToSend = `${movie.Title} is a ${
// movie.Actors
// } starer ${movie.Genre} movie, released in ${
// movie.Year
// }. It was directed by ${movie.Director}`;
// console.log(`Generated response as ${responseDataToSend}`);
// agent.add(responseDataToSend);
agent.add(`Okay! So there you go.`);
agent.add(new Card({
title: `${movie.Title}(${movie.Year})`,
imageUrl: movie.Poster,
text: `${movie.Rated} | ${movie.Runtime} | ${movie.Genre} | ${movie.Released} (${movie.Country})`,
buttonText: 'Website',
buttonUrl: movie.Website || `https://www.imdb.com/title/${movie.imdbID}`
}));
agent.add(new Suggestion(`More details`));
agent.add(new Suggestion(`Another movie`));
return Promise.resolve(agent);
}, error => {
console.log(`Got an error as ${error}`);
agent.add(`Sorry bout that! An error prevented getting data for: ${agent.parameters.movie || 'the requested movie'}`
);
})
.catch(function (err) {
console.log(`Caught an err as ${err}`);
agent.add(err);
});
// agent.add(`This message is from Dialogflow's Cloud Functions for Firebase editor!`);
// const newCard = new Card({
// title: `Title: this is a card title`,
// imageUrl: imageUrl,
// text: `This is the body text of a card. You can even use line\n breaks and emoji! 💁`,
// buttonText: 'This is a button',
// buttonUrl: linkUrl
// });
// // newCard.setPlatform('facebook');
// agent.add(newCard);
// agent.add(new Suggestion(`Quick Reply`));
// agent.add(new Suggestion(`Suggestion`));
// agent.setContext({ name: 'weather', lifespan: 2, parameters: { city: 'Rome' }});
}
function moreDetailsOther(agent) {
return getMovieDataFromOMDb(agent.parameters.movie).then(movie => {
agent.add(`Okay! I've got you covered on this too.`);
agent.add(`So the ${movie.Actors} starer ${movie.Type} is produced by ${movie.Production}, is directed by ${movie.Director}`);
agent.add(`It ${movie.Awards}. It's available in ${movie.Language}`);
agent.add(`Written by ${movie.Writer}, it plots ${movie.Plot}`);
agent.add(new Suggestion(`Stats on the movie`));
agent.add(new Suggestion(`Another movie`));
return Promise.resolve(agent);
}, error => {
console.log(`Got an error as ${error}`);
agent.add(`Sorry bout that! An error prevented getting data for: ${agent.parameters.movie || 'the requested movie'}`
);
})
.catch(function (err) {
console.log(`Caught an err as ${err}`);
agent.add(err);
});
}
function movieStatsOther(agent) {
return getMovieDataFromOMDb(agent.parameters.movie).then(movie => {
let ratingDetails = `${movie.Title} scored `;
movie.Ratings.forEach(rating => {
ratingDetails += `${rating.Value} on ${rating.Source} `
});
agent.add(`Sure! Here are the stats.`);
agent.add(ratingDetails);
agent.add(new Suggestion(`Another movie`));
return Promise.resolve(agent);
}, error => {
console.log(`Got an error as ${error}`);
agent.add(`Sorry bout that! An error prevented getting data for: ${agent.parameters.movie || 'the requested movie'}`
);
})
.catch(function (err) {
console.log(`Caught an err as ${err}`);
agent.add(err);
});
}
function getMovieDataFromOMDb(movieName) {
const movieToSearch = movieName || 'The Godfather';
const options = {
uri: 'https://www.omdbapi.com/',
json: true,
qs: {
t: movieToSearch,
apikey: API_KEY
}
};
return rp(options);
}
// Run the proper handler based on the matched Dialogflow intent
let intentMap = new Map();
intentMap.set('Default Welcome Intent', welcome);
intentMap.set('Default Fallback Intent', fallback);
if (agent.requestSource === agent.ACTIONS_ON_GOOGLE) {
intentMap.set(null, googleAssistantOther);
// intentMap.set('More Details', googleAssistantMoreDetails);
// intentMap.set('Movie Stats', googleAssistantMovieStats);
} else {
intentMap.set('Get Movie Details', getMovieDetailsOther);
intentMap.set('More Details', moreDetailsOther);
intentMap.set('Movie Stats', movieStatsOther);
}
agent.handleRequest(intentMap);
});
server.listen(process.env.PORT || 8000, () => {
console.log('Server is up and running...');
});
Codepen: https://codepen.io/siddajmera/pen/eraNLW?editors=0010
You don't show all your code, but it looks like getDistance() is your Intent Handler function that you've registered in code that you're not showing.
If so, and if you're making an asynchronous call to an API, you need to return a Promise to indicate that you're waiting for the HTTP call to complete before you send the result.
Without the Promise, the function completes right after it makes the call with http.get(), but without anything being set for the response with app.add().
While it is possible to turn an event-based call (what you're doing now) into a Promise, it isn't that easy if you're not familiar with it, and there are better solutions.
Using a package such as request-promise (and more likely request-promise-native - it uses the same syntax, but request-promise has the documentation) is far easier. With this, you would return the Promise that is generated by the http call, and in the then() clause of it, you would make your calls to app.add().
I haven't tested it, but it might look something like this:
const rp = require('request-promise-native');
function getDistance(app){
console.log('testing1');
let defaultPath = 'maps.googleapis.com/maps/api/distancematrix/json?units=metric';
let APIKey = '&key=<API KEY HERE>';
let origin = 'Tampines Avenue 1, Temasek Polytechnic';
let destination = 'Tampines Central 5, Tampines Mall';
let newPath = defaultPath+ '&origins=' + origin + '&destinations=' +destination + APIKey;
let url = 'https://'+newPath;
console.log(url);
rp.get(url)
.then( response => {
console.log(response);
let distance = response.rows[0].elements.distance.text
let travelTime = response.rows[0].elements.duration.text
app.add('distance between is ' + distance + '. Time taken is ' + travelTime);
})
.catch( err => {
console.log( err );
app.add('Something went wrong.');
});
};

Error 500 in Firebase functions during Like & Dislike feature

I am currently watching a tutorial bought from udemy, and during the implementation, I encountered a problem while implementing the Like or Dislike feature.
When I make a post request to a Firebase function I get a error 500 in the console, and in the Firebase functions logs I see this error:
Value for argument "documentPath" is not a valid resource path. Path must be a non-empty string.
at Object.validateResourcePath (/srv/node_modules/#google-cloud/firestore/build/src/path.js:403:15)
at CollectionReference.doc (/srv/node_modules/#google-cloud/firestore/build/src/reference.js:1718:20)
at exports.updateLikesCount.functions.https.onRequest (/srv/lib/index.js:24:44)
at cloudFunction (/srv/node_modules/firebase-functions/lib/providers/https.js:57:9)
at /worker/worker.js:783:7
at /worker/worker.js:766:11
at _combinedTickCallback (internal/process/next_tick.js:132:7)
at process._tickDomainCallback (internal/process/next_tick.js:219:9)
Index.js file functions for firebase
import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
admin.initializeApp(functions.config().firebase);
// // Start writing Firebase Functions
// // https://firebase.google.com/docs/functions/typescript
export const updateLikesCount = functions.https.onRequest((request, response) => {
console.log(request.body);
const eventId = request.body.eventId;
const userId = request.body.userId;
const state = request.body.state; //like or unlike
// tslint:disable-next-line: no-floating-promises
admin.firestore().collection("events").doc(eventId).get().then((data: any) => {
let likesCount = data.data().likesCount || 0;
let likes = data.data().likes || [];
let updateData = {} as any;
if (state === "like") {
updateData["likesCount"] = ++likesCount;
updateData[`likes.${userId}`] = true;
}
else {
updateData["likesCount"] = --likesCount;
updateData[`likes.${userId}`] = false;
}
admin.firestore().collection("events").doc(eventId).update(updateData).then(() => {
response.status(200).send("Done");
}).catch((err) => {
response.status(err.code).send(err.message);
})
}).catch((err) => {
response.status(err.code).send(err.message);
})
})
Feed.html
<div class = "content" *ngFor="let event of events">
<ion-button (click)="like(event)">Like</ion-button>
...
Feed.page.ts
like(event) {
let body = {
eventId: event.id,
userId: firebase.auth().currentUser.uid,
state: event.data().likes && event.data().likes[firebase.auth().currentUser.uid] == true ? "unlike" : "like",
}
// tslint:disable-next-line: max-line-length
this.http.post("https://us-central1-cp-eventoo.cloudfunctions.net/updateLikesCount", JSON.stringify(body), { responseType: "text" }).subscribe((data) => { //third parameter represents the response(200) in functions
console.log(data)
}, (error) => {
console.error(error.status);
})
}
When i click like button i get Error 500, when i test with postman i get this error:
Value for argument "documentPath" is not a valid resource path. Path must be a non-empty string.
at Object.validateResourcePath (/srv/node_modules/#google-cloud/firestore/build/src/path.js:403:15)
at CollectionReference.doc (/srv/node_modules/#google-cloud/firestore/build/src/reference.js:1718:20)
at exports.updateLikesCount.functions.https.onRequest (/srv/lib/index.js:24:44)
at cloudFunction (/srv/node_modules/firebase-functions/lib/providers/https.js:57:9)
at /worker/worker.js:783:7
at /worker/worker.js:766:11
at _combinedTickCallback (internal/process/next_tick.js:132:7)
at process._tickDomainCallback (internal/process/next_tick.js:219:9)
if (typeof (request.body) === 'string') {
body = JSON.parse(request.body);
} else {
body = request.body;
}
By adding these lines of code the problem is gone.Thanks!
if (!is.string(resourcePath) || resourcePath === '') {
throw new Error(`Path must be a non-empty string.`);
}
This is the line that is failing for you, your postman raw data needs to be in JSON format. By default is set to text, it must be changed to JSON(application/json)
Also, make sure your function returns a promise
export const updateLikesCount = functions.https.onRequest((request, response) => {
console.log(request.body);
const eventId = request.body.eventId;
const userId = request.body.userId;
const state = request.body.state; //like or unlike
return admin.firestore().collection("events").doc(eventId).get().then((data: any) => {
let likesCount = data.data().likesCount || 0;
let likes = data.data().likes || [];
let updateData = {} as any;
if (state === "like") {
updateData["likesCount"] = ++likesCount;
updateData[`likes.${userId}`] = true;
}
else {
updateData["likesCount"] = --likesCount;
updateData[`likes.${userId}`] = false;
}
return admin.firestore().collection("events").doc(`${eventId}`).update(updateData).then(() => {
response.status(200).send("Done");
}).catch((err) => {
response.status(err.code).send(err.message);
})
}).catch((err) => {
response.status(err.code).send(err.message);
})
})
I added a couple of return statements for your functions knows when to be finished, also ensure the doc is using the eventId as a string.
Lastly, I dont need to JSON.stringify(body) your payload, sending the object as it is it;s perfectly fine.

wit.ai Bot Engine Stories connected to hubot

I am trying to get the new wit.ai Bot Engine connected to hubot with Javascript.
Unfortunately I am not a JS Developer so I'm struggling.
Here's the code I have:
'use strict';
const Wit = require('../../../node-wit').Wit;
const firstEntityValue = (entities, entity) => {
const val = entities && entities[entity] &&
Array.isArray(entities[entity]) &&
entities[entity].length > 0 &&
entities[entity][0].value
;
if (!val) {
return null;
}
return typeof val === 'object' ? val.value : val;
};
const actions = {
say: (sessionId, msg, cb) => {
console.log(msg);
cb();
},
merge: (context, entities, cb) => {
const loc = firstEntityValue(entities, "location");
if (loc) context.loc = loc;
cb(context);
},
error: (sessionId, msg) => {
console.log('Oops, I don\'t know what to do.');
},
'fetch-weather': (context, cb) => {
// Here should go the api call, e.g.:
// context.forecast = apiCall(context.loc)
context.forecast = 'sunny';
cb(context);
},
};
const client = new Wit('MY_TOKEN_HERE', actions);
client.interactive();
module.exports = function(robot) {
robot.respond(/hey\s+(.+$)/i, function(msg){
var match = msg.match[1];
msg.send("I've heared: " + match);
console.log(match)
process.stdout.write(match);
});
}
The script listens to "hey botname" and outputs what was written after this. My Problem is I have no clue how to get this input sent to the wit client. Using this script in bash without the hubot stuff works fine towards wit as this is based on the quick start example from wit.ai.
The other issue I'm facing is that I would like to have hubot listen in a private channel with every user and have it respond to every message without prefix. Just as the node example does in the console.
Help is highly apprechiated!
Ok, after fiddling a while I made this work.
Here's what my hubot script looks like now:
'use strict';
const Wit = require('../../../node-wit').Wit;
var room;
const firstEntityValue = (entities, entity) => {
const val = entities && entities[entity] &&
Array.isArray(entities[entity]) &&
entities[entity].length > 0 &&
entities[entity][0].value
;
if (!val) {
return null;
}
return typeof val === 'object' ? val.value : val;
};
const actions = {
say: (sessionId, msg, cb) => {
console.log(msg);
room.send(msg)
cb();
},
merge: (context, entities, cb) => {
const loc = firstEntityValue(entities, "location");
if (loc) context.loc = loc;
cb(context);
},
error: (sessionId, msg) => {
console.log('Oops, I don\'t know what to do.');
room.send('Oops, I don\'t know what to do.')
},
};
const client = new Wit('MY_TOKEN_HERE', actions);
//client.interactive();
module.exports = function(robot) {
robot.listen(function(msg) {
var userID = msg.user.id;
var roomID = msg.user.room;
// is this a direct chat(private room)?
if(roomID.indexOf(userID) >= 0){
if(typeof msg.text == "string"){
client.pxMessage(msg.text);
}
}
return true;
},
function(response){
// save room for replys
room = response;
});
}
additionally I made an awful hack to wit.js to get this work. I added the following function as I was not able to use the available methods to get this working. Basically callbacks and session were holding me back:
this.pxMessage = (message) => {
const sessionId = uuid.v1();
const context = {};
const steps = 5;
this.runActions(
sessionId,
message,
context,
(error, context) => {
if (error) {
l.error(error);
}
rl.prompt();
},
steps
);
}
If someone would take this further and implement it properly I would love to see the result. This hack works and we now have a really smart bot in our rocket.chat who understands natural language and learns every day.
You can directly use this module, it seems to be working well: https://www.npmjs.com/package/hubot-wit
I have just now finished integration. You just need to provide the environment variable WIT_TOKEN and it works beautifully!

Categories

Resources