I am building several bots with DialogFlow and Hangouts Chat integration.
How can I retrieve the user email of the user spraking to the bot ?
When I see the request sent from Hangouts chat to Dialogflow I cannot see any information about the user, it's like an anonymous request.
Has anybody find a workaround for that ?
It can be retrieved using events:
For each event like a message, a new user added or removed to the chat you can call event.user and it has the following fields:
event.user.name: The user name
event.user.email: The user email
event.user.displayName: The 'display' user name
event.user.avatarUrl: The user avatar's img url
For example, a working code using onMessage(event) function, for each interaction with the bot, the bot will answer with the user name and the message:
function onMessage(event) {
if (event.type == "MESSAGE" || event.space.type == "DM" ) {
var message = event.user.displayName + " said " + event.message.argumentText;
return { "text": message };
}
}
Response:
Benoit said How to get usernames on Hangouts bots?
A diagram of the JSON event format on Hangouts:
More documentation on hangouts events format and examples of use
Ok. Figured it out...
Indeed, you'll need to handle the intent using a Fullfilment.
In the default fullfilment function, you'll see this bit of code:
exports.dialogflowFirebaseFulfillment = functions.https.onRequest((request, response) => {
const agent = new WebhookClient({ request, response });
console.log('Dialogflow Request headers: ' + JSON.stringify(request.headers));
console.log('Dialogflow Request body: ' + JSON.stringify(request.body));
//... REST OF THE CODE WHERE YOU 'LL HANDLE THE INTENTS
});
The event info that you normally get out of an apps script chat bot is in the
request.body
For example email:
const email = request.body.originalDetectIntentRequest.payload.data.event.user.email;
In that user object, you'll also find:
email
avatarUrl
displayName
Ok, solution here is to enable fulfilment and process this as a cloud function. The input json of the cloud function contains all the event json.
I wanted to reply back "Hello #name" without using cloud function
Related
I am trying to make a Google Home app and I am using webhooks with express.js running on a local server as fulfilment. I would like to use the "request.body.originalDetectIntentRequest.payload.user.userStorage" parameter from the request to identify a user when the webhook sends a post request to my server. The userId given with this request is equal to a log-filename that I store locally for each user where I keep track of different things per user.
I got the idea of handling things like this from an earlier post here on stack overflow. In the answer given by Prisoner, he talks about the importance of the response that you have to send back. So far so good, I tried to implement this in my code by sending a response once the new userId is made but I keep getting the same error :
(node:21236) UnhandledPromiseRejectionWarning: Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
at ServerResponse.setHeader (_http_outgoing.js:518:11)
So it probably has to do with the fact that I have a line agent.handleRequest(intentMap); at the end of my code which will handle the request and send a response back to the user itself.
How do I solve this? I already tried to find a solution for this problem but the after trying different things, there is only one thing I can think of which would be to expand the response before sending it with the payload.google.userStorage variable even though I have tried response.write but that was giving me the same error.
My code:
app.post('/', express.json(), (request, response) => {
const agent = new WebhookClient({ request, response });
console.log('Dialogflow Request headers: ' + JSON.stringify(request.headers));
console.log('Dialogflow Request body: ' + JSON.stringify(request.body));
let userStorage = request.body.originalDetectIntentRequest.payload.user.userStorage || JSON.stringify({});
let userId;
console.log("userStorage", userStorage);
userStorage = JSON.parse(userStorage);
console.log("userStorage_after_parsing", userStorage);
if (userStorage.hasOwnProperty('userId')) {
userId = userStorage.userId;
console.log('user was already defined:' + userId);
} else {
let uuid = require('uuid/v4');
checkNewUser = true;
userId = uuid();
userStorage.userId = userId
console.log(userId);
response.send({
'payload': {
'google': {
'userStorage': JSON.stringify(userStorage)
}
}
}
);
//rest of the code
agent.handle(intentMap)
UPDATED QUESTION:
After prisoner's answer, I decided to include the code of the handleWelcome intent where I would like to paste the code into to extend the response with the the userStorage variable:
function handleWelcome(agent) {
//code to add userStorage to the response?
if (checkIfUserVerified() === true) {
agent.add('Hi.');
//updateLog('User just said hi.');
let userObj = readJsonSync(userId + '.json');
userObj.logs.push('User just started talking to this chatbot ' + getCurrentDateTime());
fs.writeFileSync(userId + '.json', JSON.stringify(userObj));
}
else {
close('Please make sure you are a verified user before using this version of SMART4MD. To do so, read the SMART4MD Voice assistant manual.');
agent.setFollowupEvent('GOODBYE');
}
}
Note the comment in the answer you cite where it says
// Make sure you include the userStorage as part of the response
"part of" the response - not the response itself. In your code, you're sending back the JSON to sent the user storage before your Intent Handler is called. Once something is sent, the connection is closed, so nothing further can be sent - including the reply you want. Which is why you get the error.
You don't show any of your Intent Handler functions, so it is difficult to give you an exact answer, but the general solution would be to set the userid in userStorage as part of a function that each handler calls.
'Until loop' analogue needed to continuously read status variable from helper function - and then (when the status variable is 'as we need it') - to resume bot conversation flow.
In my bot (botbuilder v.3.15) I did the following:
During one of my dialogues I needed to open external url in order
to collect some information from the user through that url.
After that I posted collected data (with conversation ID and other info) from that url to my bot app.js
file
After that I needed to resume my bot conversation
For that I created helper file - helper.js in which 'marker' variable is 'undefined' when the data from url is not yet collected, and 'marker' variable is some 'string' when the data is collected and we can continue our bot conversation
helper.js
var marker;
module.exports = {
checkAddressStatus: function() {
return marker;
},
saveAddressStatus: function(options) {
marker = options.conversation.id;
}
}
I can successfully update variable 'marker' with my data, by calling saveAddressStatus function from app.js.
However, when I get back to writing my code which is related to bot conversation flow (To the place in code after which I opened url - in file address.js, and from where I plan to continuously check the 'marker' variable whether it is already updated - in order to fire 'next()' command and continue with session.endDialogWithResult -> and then -> to further bot conversation flows - I cannot find the equivalent of 'until loop' in Node.js to resume my session in bot dialog - by returning 'next()' and continuing with the bot flow.
address.js
...
lib.dialog('/', [
function (session, args, next) {
...
next();
},
function (session, results, next) {
// Herocard with a link to external url
// My stupid infinite loop code, I tried various options, with promises etc., but it's all not working as I expect it
while (typeof helper.checkAddressStatus() == 'undefined') {
console.log('Undefined marker in address.js while loop')
}
var markerAddress = helper.checkAddressStatus();
console.log(markerAddress);
next(); // THE MOST IMPORTANT PART OF THE CODE - IF markerAddress is not 'undefined' - make another step in dialog flow to end dialog with result
function(session, results) {
...session.endDialogWithResult({markerAddress: markerAddress})
}
...
Any ideas how to make a very simple 'until loop' analoque in this context - work?
Having your bot stop and wait for a response is considered bad practice. If all of your bot instances are stuck waiting for the user to fill out the external form, your app won't be able to process incoming requests. I would at least recommend adding a timeout if you decide to pursue that route.
Instead of triggering your helper class in the endpoint you created, you should send a proactive message to the user to continue the conversation. To do this, you will need to get the conversation reference from the session and encode it in the URL that you send to the user. You can get the conversation reference from the session - session.message.address - and at the very least you will need to encode the bot id, conversation id, and the serviceUrl in the URL. Then when you send the data collected from the user back to the bot, include the conversation reference details for the proactive message. Finally, when your bot receives the data, recreate the conversation reference and send the proactive message to the user.
Here is how your conversation reference should be structured:
const conversationReference = {
bot: {id: req.body.botId },
conversation: {id: req.body.conversationId},
serviceUrl: req.body.serviceUrl
};
Here is an example of sending a proactive message:
function sendProactiveMessage(conversationReference ) {
var msg = new builder.Message().address(conversationReference );
msg.text('Hello, this is a notification');
msg.textLocale('en-US');
bot.send(msg);
}
For more information about sending proactive messages, checkout these samples and this documentation on proactive messages.
Hope this helps!
After trying and trying countless times, I ask for your help to call a Dialogflow event (GoogleHome) with a specific GoogleHome device.
Through nodeJS I managed to successfully call a Dialogflow event and I get the fullfillment response. All perfect, only I have to let my GoogleHome device speak with fullfillment, I do not need a text-only answer.
My goal is to let my GoogleHome device speak first, without the word "Ok, Google" and wait for a response from the user.
I did not find anything on the web, my attempts stop to invoke the Dialogflow event and have a console response.
This is the code i have tried for fullfillment
test: async function () {
console.log("[funcGHTalk|test] CALLED");
const projectId = "[[projectid]]";
const LANGUAGE_CODE = 'it-IT';
let eventName = "[[eventname]]";
const sessionId = uuid.v4();
const sessionClient = new dialogflow.SessionsClient();
const sessionPath = sessionClient.sessionPath(projectId, sessionId);
// The text query request.
const request = {
session: sessionPath,
queryInput: {
event: {
name: eventName,
languageCode: LANGUAGE_CODE
},
},
};
// Send request and log result
const responses = await sessionClient.detectIntent(request);
console.log('Detected intent');
const result = responses[0].queryResult;
console.log(result);
console.log(` Query: ${result.queryText}`);
console.log(` Response: ${result.fulfillmentText}`);
if (result.intent) {
console.log(` Intent: ${result.intent.displayName}`);
} else {
console.log(` No intent matched.`);
}
}
The code you have written is using the Dialogflow Detect Intent API. This is meant to run on consoles and servers to send a message to Dialogflow, which will parse it, determine which Intent it matches, call fulfillment with that information, and return all the results.
You don't need to run this on a Google Home, since the Google Assistant does all this for you.
What I think you're looking for is to develop fulfillment with Actions on Google and the Dialogflow Fulfillment API. This handles things on the other end - after Dialogflow determines what Intent matches what the user has said, and if that Intent has fulfillment enabled, it will send the information to your webhook which is running on a cloud server somewhere. You would then process it, send a reply (either using the actions-on-google library or the dialogflow-fulfillment library is easiest), and it would send it back to the Assistant.
You indicated that you want the Action to "let my GoogleHome device speak first, without the word "Ok, Google" and wait for a response from the user". This is much more complicated, and not really possible to do with the Google Home device right now. Most Actions have the user initiating the conversation with "Ok Google, talk to my test app" or whatever the name of the Action is.
You don't indicate how you expect to trigger the Home to begin talking, but you may wish to look into notifications to see if those fit your model, however notifications don't work with the Home right now, just the Assistant on mobile devices.
I am trying to make an SMS conversation platform where a user would enter yes or no which will work as a boolean check and on true Twilio server will reply with 'response 1' and on false Twilio, the server will reply with 'response 2'? How will that work in Node.js? All the library just talk about basic sending and receiving but not about changing the reply based on the message which is received.
Twilio developer evangelist here.
When you receive a message to your Twilio number, Twilio makes an HTTP request (a webhook) to a URL you provide. On the end of that URL is your application which decides how to respond. The webhook sends all the details about the message, so you can use that to make your response.
For example, if you were using Express to respond to the webhook, then you're route might look like this:
const MessagingResponse = require('twilio').twiml.MessagingResponse;
app.post('/messages', (req, res) => {
let message = req.body.Body;
message = message.trim().toLowerCase();
const twiml = new MessagingResponse();
if (message === 'yes') {
twiml.message('You said "yes"! Fantastic!');
else if (message === 'no') {
twiml.message('You said "no". That's a shame.');
} else {
twiml.message('Please reply with a "yes" or a "no". Thank you.');
}
res.header('Content-Type', 'application/xml');
res.send(twiml.toString());
});
In this case, the Body property of the request body is the message that was sent to your Twilio number and you can use conditionals to reply depending on what it said.
Let me know if that helps at all.
I have a webhook setup, and I am able to receive messages and reply to them. I would like to have the responses sent by my webhook to have messagingServiceSid attached to them.
I didn't find on documentation a way to configure that for responses from my webhook, only for new SMS using
client.sendMessage({
messagingServiceSid: 'MG9752274e9e519418a7406176694466fa',
to: '+16518675309',
body: 'Phantom Menace was clearly the best of the prequel trilogy.'
}, function(err, message) {
console.log(message);
});
Is there something similar for this code? Is it doable through the UI?
app.post('/foo/bar/sms', twilio.webhook({
host:'gassy-ocelot-129.herokuapp.com',
protocol:'https'
}), function(request, response) {
var twiml = new twilio.TwimlResponse();
twiml.message('This HTTP request came from Twilio!');
response.send(twiml);
});
Images:
No messagingService on reply messages sent using twiml response
Message Detail view from logs
Twilio developer evangelist here.
As far as I'm aware, there's no way to reply to message from a message service with TwiML.
However, rather than using TwiML, you could just send the SMS back to your user from the REST API and return an empty <Response> to the incoming webhook. Something a bit like this:
app.post('/foo/bar/sms', twilio.webhook({
host:'gassy-ocelot-129.herokuapp.com',
protocol:'https'
}), function(request, response) {
// send the message from the message service
client.sendMessage({
messagingServiceSid: 'MG9752274e9e519418a7406176694466fa',
to: request.body.From,
body: 'Your message'
}, function(err, message) {
console.log(message);
});
// send empty TwiML response
var twiml = new twilio.TwimlResponse();
response.send(twiml);
})
Let me know if that helps at all.
If you receive an incoming SMS on a phone number currently set up to that Messaging Service (via the web ui or phone number REST), then the incoming requests will have MessagingServiceSid in the query string.