Coben Yap
Coben Yap

Reputation: 69

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

enter image description here

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)

Upvotes: 1

Views: 659

Answers (2)

Prisoner
Prisoner

Reputation: 50741

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.');
      });

};

Upvotes: 0

Lajos Arpad
Lajos Arpad

Reputation: 77063

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

Upvotes: 1

Related Questions