Actions on Google Nodejs getDeviceLocation - node.js

I'm working on learning Dialogflow with Google Actions and I'm a bit lost with thew new v2 actions-on-google.
I'm attempting to get a device location, however the method I thought was correct is returning undefined.
I feel like I'm missing something simple but I haven't been able to find it.
The code is:
const functions = require('firebase- functions');
const { dialogflow, Permission } = require('actions-on-google');
const app = dialogflow();
app.intent('create_log', conv => {
conv.ask(new Permission({
context: 'To locate you',
permissions: 'DEVICE_PRECISE_LOCATION'
}));
});
app.intent('user_info', (conv, params, granted) => {
if (granted) {
const coordinates = app.getDeviceLocation().coordinates;
if (coordinates) {
conv.close(`You are at ${coordinates}`);
} else {
// Note: Currently, precise locaton only returns lat/lng coordinates on phones and lat/lng coordinates
// and a geocoded coordinates on voice-activated speakers.
// Coarse location only works on voice-activated speakers.`enter code here`
conv.close('Sorry, I could not figure out where you are.');
}
} else {
conv.close('Sorry, I could not figure out where you are.');
}
});

I don't think you'd want any method on app, even if that one existed, since app is available for all Intent Handler calls. Information specific to that conversation is in the conv parameter passed to the Intent Handler.
You probably want something more like
const coordinates = conv.device.location;

Related

Implementation of a global search for chats in telegram tdlib

I'm trying to replicate the global chat search as in telegram.
I'm using getChats method for searching, but the problem is that the method only returns a list of ids.
In addition to the id, I would also like to get the name and avatar of the chat.
Therefore, I have to go through the chatids in the forEach and for each id call the getChat method that returns the data I need. This, in turn, causes severe problems with the query execution time. (14 seconds). In a telegram, the search takes ~2 seconds. I don’t know how they did it, I re-read all the documentation and did not find a method that would allow me to pass the name of the chat and get, in addition to identifiers, also a title and an image. Has anyone already encountered a similar problem?
import BaseAction from "./BaseAction";
import airgram from "../airgram/airgram";
import { ChatsUnion, ChatUnion } from 'airgram';
class SearchChatsAction implements BaseAction
{
async run(name: string): Promise<any>
{
const output = await airgram.api.searchPublicChats({
query: name
});
const promises: Array<any> = [];
const result: Array<any> = [];
for (const chatId of (output.response as ChatsUnion).chatIds)
{
promises.push(
airgram.api.getChat({
chatId: chatId
}).then(output => {
result.push({
id: (output.response as ChatUnion).id,
title: (output.response as ChatUnion).title
});
})
);
}
await Promise.all(promises);
return result;
}
}
export default SearchChatsAction;
I think the issue you're facing is because of API. You should try using different API. If you check these two documentations:
searchPublicChat
searchPublicChats
The API you're using returns just chatIds but searchPublicChat will contain all the information of searched chat.

How do I best modularize dialogs with Microsoft Bot Framework (v3)?

I've just been given a project where I'll need to learn and deal with Microsoft Bot Framework, version 3.2 (it's an old project that I'm modifying).
I'm working through the examples and trying to understand how dialog flow works, and how I might best modularize it.
As I understand it, when you create your bot like this
// CODE SAMPLE 1
const bot = new builder.UniversalBot(connector, [
function (session) {
session.send("Welcome to the dinner reservation.");
session.beginDialog('askForDateTime');
},
/*
functions omitted for brevity
*/
]).set('storage', inMemoryStorage); // Register in-memory storage
the array of functions that comprise your "default dialog" must be given when the bot is created -- that is, you can't add the default dialog at a later point, or change it. Is that correct?
Then, later, if you want to modularize your dialog structure, you can have something like this (referring to the code above)
// CODE SAMPLE 2 (in same file as code above)
bot.dialog('askForDateTime', [
function (session) {
builder.Prompts.time(session, "Please provide a reservation date and time (e.g.: June 6th at 5pm)");
},
function (session, results) {
session.endDialogWithResult(results);
}
]);
So, is bot.dialog registering this dialog with the bot for later use? That is, there's a look-up of some sort at run-time -- during the conversation -- based on this string that connects session.beginDialog('askForDateTime'); in the first code sample with the functions registered with bot.dialog('askForDateTime') in the second code sample?
When I looked at the SDK reference, I see that beginDialog accepts an IAddress
function beginDialog(address: IAddress, dialogId: string, dialogArgs?: any, done?: (err: Error) => void)
where it says
Address routing information for an event. Addresses are bidirectional
meaning they can be used to address both incoming and outgoing events.
They're also connector specific meaning that connectors are free to
add their own fields to the address.
So this 'registration' via string is basically an event registration system, kind of like addEventListener, but in this case it's not registering an action per se, but a dialog?
Two last questions:
Can one call session.beginDialog from within a bot.dialog? That is, have a nested tree of dialogs? As it is, the only example is of nesting from the default dialog, but I didn't know if it could go deeper.
Finally, how can one modularize your dialogs into separate node modules, that is, move your sub-dialogs into separate files? I thought of something like this:
// askForDateTime.js
module.exports = bot =>
bot.dialog('askForDateTime', [
function (session) {
builder.Prompts.time(session, "Please provide a reservation date and time (e.g.: June 6th at 5pm)");
},
function (session, results) {
session.endDialogWithResult(results);
}
]);
but don't see how to use it in my main app
// app.js
const askDateTimeDialog = require('./askForDateTime')(bot) // how to use this? I need to pass in a bot that's not yet created. Do I even need to import it?
const bot = new builder.UniversalBot(connector, [
function (session) {
session.send("Welcome to the dinner reservation.");
session.beginDialog('askForDateTime'); // <--- how to beginDialog with imported dialog? Or is the 'registration' behind the scenes sufficient since I'm just using the same string?
},
/*
functions omitted for brevity
*/
]).set('storage', inMemoryStorage); // Register in-memory storage
Thanks for any help! I realize this probably all is easier with version 4, but I need to use an earlier version.
First and foremost, please be aware the Botbuilder V3 is in sunset with support ending at the end of 2019. You can read more about this topic here and options for migrating from v3 to v4 here.
Regarding default dialogs, the other method is to only pass the connector into the adapter and then start a dialog when the conversationUpdate occurs. There are some challenges that can bubble up when using conversationUpdate, so I would look over this blog post before continuing.
var bot = new builder.UniversalBot(connector)
bot.on('conversationUpdate', function (message) {
if (message.membersAdded) {
message.membersAdded.forEach(function (identity) {
if (identity.id === message.address.bot.id) {
bot.beginDialog(message.address, '/main');
}
});
}
});
// ----greetings.js -----
bot.dialog('/main', [
function (session, args, next) {
session.send("Glad you could join.");
session.beginDialog('/nextDialog');
}
]);
Regarding beginDialog() registration/addressing, there is no pre-registeration that occurs. When a dialog is called, it's essentially called like any other function is. The address, more or less, is used for managing dialog state (i.e. where a bot is in a conversation) - whether a dialog is being added to stack, is in use, or being popped off the stack.
Regarding calling one dialog within another, yes it is doable, as you can see in this sample:
lib.dialog('/', [
[...other functions...]
function (session, args) {
session.dialogData.recipientPhoneNumber = args.response;
session.beginDialog('validators:notes', {
prompt: session.gettext('ask_note'),
retryPrompt: session.gettext('invalid_note')
});
},
[...other functions...]
]);
Lastly, regarding modularizing, yes, this is also doable. Look over either of these two samples. core-MultiDialogs is the less complicated but demo-ContosoFlowers is also a good example to reference.
Hope of help!

Custom fallback intents when using confirmation helper

I'm trying to create custom fallbacks for intents that contain confirmations. Here is the code:
const functions = require('firebase-functions');
const {
dialogflow,
Confirmation
} = require('actions-on-google');
const app = dialogflow({
debug: true,
});
app.intent('vitals-confirmation', (conv, input, confirmation) => {
conv.ask(new Confirmation(`Great! Have you fainted recently?`));
});
app.intent('vitals-confirmation-fallback', (conv, input, confirmation) => {
conv.ask(new Confirmation(`Sorry I didn't understand what you said. Did you faint?`));
})
app.intent('S1-confirmation', (conv, input, confirmation) => {
if (confirmation) {
conv.ask(new Confirmation(`I have recorded that you have fainted. Have your feet been hurting?`));
} else {
conv.ask(new Confirmation(`I have recorded that you have not fainted. Have your feet been hurting?`));
}
});
My app asks the user if they have fainted in "vitals-confirmation" and the user is expected to answer with a yes or no type answer that will be identified by the confirmation helper, if they do this correctly they will go to "S1-confirmation" and will be asked the next question.
However the following is outputted when I respond with an answer that is not a yes/no type answer (for example: "red"):
Sorry, Great! Have you fainted recently?
It seems as though there is a default fallback that responds with "Sorry, [repeats previous text output]" and does not go to a custom fallback intent which I have created (which is my desired result).
Take a look at the documentation for Confirmation helper of Actions SDK for Node.js.
You have to setup an intent with the actions_intent_CONFIRMATION event in DialogFlow in order to retrieve the user response. My advice is to check how you configured your intents and use this method, otherwise be sure to create the follow-up intents with the desired context lifespan.
Example from documentation:
app.intent('Default Welcome Intent', conv => {
conv.ask(new Confirmation('Are you sure you want to do that?'))
})
// Create a Dialogflow intent with the `actions_intent_CONFIRMATION` event
app.intent('Get Confirmation', (conv, input, confirmation) => {
if (confirmation) {
conv.close(`Great! I'm glad you want to do it!`)
} else {
conv.close(`That's okay. Let's not do it now.`)
}
})

dialogflow fullfilment and firebase response time

I am trying to build a simple chatbot with DialogFlow.
My aim is to give information from user question, like : where can I slackline above water in croatia ? I have two parameters (croatia, waterline) and a list of slackline places.
So I need a data base to retrieve information from parameters. DialogFlow allows fulfillment with Firebase. I build a database with places (name, country, type of slack) and enable webhook call for my intent.
I use Inline Editor and index.js
const parameters = request.body.queryResult.parameters;
var country = parameters.country.toString();
function show(snap) {
console.log('snap');
agent.add(JSON.stringify(snap.val(),null,2));
}
function slkplc(agent) {
var testRef;
firebase.database().ref('slackplace').once('value',show);
}
// Run the proper function handler based on the matched Dialogflow intent name
let intentMap = new Map();
intentMap.set('slack place', slkplc);
agent.handleRequest(intentMap);
But I do not get the expected result while trying it on DialogFlow or Google Assistant. The function show is asynchronously called but too late and the response is not available for DialogFlow :
I see three way to deal with this problem :
use blocking call to database : another database ?
treat asynchronous message with DialogFlow ???
response to user that an error occured.
The third that I choose, but it is always on error.
After trying several things to wait data from database response, the only thing I managed is to freeze the response, therefore the timeout of DialogFlow - 5s -and Firebase - 60s - were reached.
A workaround
Another way to do it is to separate database acquisition and request/response from DialogFlow. The data of database is collected outside of the dialogflowFirebaseFulfillment
var data;
var inidata = firebase.database().ref().on('value',function(snap) {
console.log('snap');
data = snap.val();
});
exports.dialogflowFirebaseFulfillment = functions.https.onRequest((request, response) => {
const agent = new WebhookClient({ request, response });
...
function slkplc(agent) {
agent.add(JSON.stringify(data,null,2));
}
// Run the proper function handler based on the matched Dialogflow intent name
let intentMap = new Map();
intentMap.set('slack place', slkplc);
agent.handleRequest(intentMap);
}
Now I can do what I want with data, and I am able to find the place where I can practice waterline in croatia. But there is always something weird, the data of the database is duplicated ...
The "right" solution is option 2 that you suggest: since you're doing an asynchronous call, you need to handle this correctly when working with the dialogflow-fulfillment library.
Basically, if your handler makes an asynchronous call, it needs to be asynchronous as well. To indicate to the handleRequest() method that your handler is async, you need to return a Promise object.
Firebase's once() method returns a Promise if you don't pass it a callback function. You can take advantage of this, return that Promise, and also handle what you want it to do as part of a .then() clause. It might look something like this:
function slkplc(agent) {
var testRef;
return firebase.database().ref('slackplace').once('value')
.then( snap => {
var val = snap.val();
return agent.add( JSON.stringify( val, null, 2 ) );
});
}
The important part isn't just that you use a Promise, but also that you return that Promise.

Dialogflow IntentHandler not found for intent: myIntent (Dialogflow V2)

Since I upgraded my Dialogflow to use v2 API, I get the following error:
Dialogflow IntentHandler not found for intent: myIntent
For some reasons, my intent is no longer recognized altough the action name is identical - myIntent.
This is how I'm using my NodeJS (express V4) app to return a response:
dialogflowApp.intent('myIntent', (conv, {version}) => {
conv.close('test');
});
What could have gone wrong?
Make sure that myIntent is spelled the same in Dialogflow and in your NodeJS webhook function, otherwise you'll get this error. This is how I write and access my functions in the webhook:
//Function execution
dialogflowApp.intent('myIntent', myIntentFunction);
//Function definition
function myIntentFunction(conv, {version}){
conv.close('test');
}
V2 Actions SDK uses the Intent name instead of the Action Name. The Intent name can be found in your request, or directly from DialogFlow intent interface
DialogFlow V1 to V2 Migration Documentation
In V2 you have to use the intent name instead of the action name. First you define this at the beginning of the index file:
'use strict';
const {dialogflow} = require('actions-on-google');
const functions = require('firebase-functions');
const app = dialogflow({debug: true});
Then you have to add the code for each intent:
app.intent('intentName1', (conv, {parameterName}) => {
conv.close('Answer text');
});
app.intent('intentName2', (conv, {parameterName}) => {
conv.ask('Answer text');
});
Finally, at the end of the index file, it is necessary to set the DialogflowApp object to handle the HTTPS POST request. Like this:
exports.dialogflowFirebaseFulfillment = functions.https.onRequest(app);
In your example:
dialogflowApp.intent('myIntent', (conv, {version}) => {
conv.close('test');
});
You should check that you have defined 'dialogflowApp' at the begining of index file:
const dialogflowApp = dialogflow({debug: true});
Then you have two options:
Replace 'myIntent' with the name of the intent
Change your intent name to be 'myIntent'
IMPORTANT: You have to make sure that the intent name in dialogflow and that in the code are exactly the same, since it is case sentive.
'version' should be the name of a parameter received from that intent.
Also check that you have this at the end of the index file:
exports.dialogflowFirebaseFulfillment = functions.https.onRequest(dialogflowApp);
Hope this can help! :)
Try to put your intent handler outside your express '/' post route:
dialogflowApp.intent('myIntent', (conv, {version}) => {
conv.close('test');
});
express().post('/', function(req, res)) {
})
According to this comment on Github, Actions-on-Google V2 does not match on the action, but instead matches on the intent.name. I have yet to find a smooth way of finding the name property of a Dialogfow intent, but I have been able to find it by looking at the Diagnostic info after testing it in the right column.
Note: What you named the intent at the top of the screen is the Display Name, not the name.
Update: Part of the intent.name can be found in the URL after opening the intent. The intent.name currently is given in the following format:
projects/<username>/agent/intents/<intent-uuid>
The intent-uuid can be found in the last part of the path:
https://console.dialogflow.com/api-client/#/agent/<agent-uuid>/editIntent/<intent-uuid>/
While this is also not ideal either, it can make things a little easier.
Try also to remove space from the intent name defined in DialogFlow and in your javascript function call. This solve the problem in my case (running in a Azure function)
before :
app.intent('Open Car Gate', conv => {
conv.close('OK, je vais ouvrir le portail en grand !');
});
after :
app.intent('OpenCarGate', conv => {
conv.close('OK, je vais ouvrir le portail en grand !');
});

Resources