Why is my webhook call fulfillment deleting my parameter in Dialogflow? - email

I'm working on a dialogflow chatbot. It retrieves the user's name and email at the start of the conversation, and I have a sessions-vars output context with a lifespan of 60 to keep these parameters throughout the conversation. I know they are kept by checking diagnostic info and referring to them in chatbot replies as #context.parameter.
Near the end of my conversation path there in an intent called 110checklistemail.sendemail where my chatbot asks the user if they want information emailed to their email or sent in the chat. If the user says "Email it to me" I have webhook call enabled, where it redirects to fulfillment code, pasted below.
I followed a guide to integrate the chatbot with sendgrid, and the email is sent and does work if I prompt for the email at that specific intent. However, if I don't prompt for the email address (eg. the user says 'email it to me') then the agent is not able to send the email because the email parameter is now blank, despite being populated before in a context with a long lifespan.
Fulfillment code is the following:
'use strict';
const functions = require('firebase-functions');
const {WebhookClient} = require('dialogflow-fulfillment');
const {Card, Suggestion} = require('dialogflow-fulfillment');
const sgMail = require('#sendgrid/mail');
process.env.DEBUG = 'dialogflow:debug'; // enables lib debugging statements
process.env.SENDGRID_API_KEY = 'SG._APIKEYHERE';
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));
function sendEmail(agent) {
sgMail.setApiKey(process.env.SENDGRID_API_KEY);
const emailParam = agent.parameters.email;
const msg = {
to: emailParam,
from: 'tyler#mailfence.com',
subject: 'Just a quick note',
text: 'Just saying Hi ${agent.parameters.given-name} from Dialogflow...',
html: 'Just saying <strong>Hi Hi ${agent.parameters.given-name} from Dialogflow</strong>...',
};
console.log(msg);
sgMail.send(msg);
agent.add(`What a beauty!`);
}
// Run the proper function handler based on the matched Dialogflow intent name
let intentMap = new Map();
intentMap.set('110checklistemail.sendemail', sendEmail);
// intentMap.set('your intent name here', yourFunctionHandler);
// intentMap.set('your intent name here', googleAssistantHandler);
agent.handleRequest(intentMap);
});
Snippet from Diagnostic info:
{
"responseId": "130742d9-7453-41c6-8b27-ee8f91d9d02d-5a74d3f9",
"queryResult": {
"queryText": "Email it to me",
"parameters": {
"email": ""
},
"allRequiredParamsPresent": true,
"fulfillmentText": "What a beauty!",
"fulfillmentMessages": [
{
"text": {
"text": [
"What a beauty!"
]
}
}
],
"outputContexts": [
{
"name": "projects/tyler-vhyo/locations/global/agent/sessions/aef22896f9/contexts/1await_checklist_type",
"lifespanCount": 4,
"parameters": {
"Jobtype.original": "custom shower",
"email.original": "",
"Jobtype": "custom shower",
"email": ""
}
},
{
"name": "projects/tyler-vhyo/locations/global/agent/sessions/aef3d36-18d696f9/contexts/session-vars",
"lifespanCount": 54,
"parameters": {
"last-name.original": "",
"email.original": "",
"email": "",
"given-name.original": "Tim",
"Jobtype": "custom shower",
"Jobtype.original": "custom shower",
"given-name": "Tim",
"last-name": ""
}
I was wondering why it wasn't working unless I prompt for the email in that specific intent, and I realised it's because the email parameters are now all blank (and definitely populated before!) I don't want to reprompt for the email when the user already has to input it at the start of the conversation.
How can I solve this issue and get the email to send off the pre-existing email parameter?
Also, how can I get the ${agent.parameters.given-name} code to reference the name parameter correctly in the email body? It hasn't been working for me and I don't know if there is a better way to pull parameters in the email.
I'm an extreme novice at coding at the code above is something I made following a guide. I really do have no idea what is happening so any advice is very welcome and appreciated. Thank you in advance!

Managed to solve the issue. Steps that I think led to it working:
Added all the parameters to 'action and parameters' of the intent
Checkmarked 'Is list'
Added a default value for each parameter referring to itself from the earlier context in the form #session-vars.parametername
I don't understand 100% why it wouldn't work without the above steps, as the intent shouldn't modify or clear the parameters, but it seems to have done the trick.

Related

POST data to Google Sheet web app from AWS Lambda

CURRENTLY
I have a Google Sheets App Script 'web app'
Script in Goolge Sheets
function doPost(e) {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss.getSheetByName("Sheet1");
sheet.getRange("A1").setValue("Hello!")
return "Success!"
}
Google Apps Script Web App Config:
Execute as: Me // or as User. I've tried both.
Who has access: Anyone within MyOrganisation
I want to make a POST request to the above Web App from AWS Lambda.
AWS Lambda .js:
const { GoogleSpreadsheet } = require("google-spreadsheet");
const doc = new GoogleSpreadsheet(
{spreadsheetId}
);
await doc.useServiceAccountAuth({
client_email: process.env.GOOGLE_SERVICE_ACCOUNT_EMAIL,
private_key: process.env.GOOGLE_PRIVATE_KEY.replace(/\\n/g, "\n"),
});
let token = doc["jwtClient"]["credentials"]["access_token"];
await new Promise((resolve, reject) => {
const options = {
host: 'script.google.com',
path: "/macros/s/{myscriptid}/exec", //<-- my web app path!
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': "Bearer "+ token
}
};
//create the request object with the callback with the result
const req = HTTPS.request(options, (res) => {
resolve(JSON.stringify(res.statusCode));
});
// handle the possible errors
req.on('error', (e) => {
reject(e.message);
});
//do the request
req.write(JSON.stringify(data));
//finish the request
req.end();
});
console.log("response:"+JSON.stringify(response))
GCP Service Account
I have a GCP Service Account, with permission to Google Sheets API, and otherwise unrestricted access.
This Service account has EDIT access to the Google Sheet with the doPost(e) script.
Token Output:
"jwtClient": {
"_events": {},
"_eventsCount": 0,
"transporter": {},
"credentials": {
"access_token": "somelongvalue...............", //<-- what I use
"token_type": "Bearer",
"expiry_date": 1661662492000,
"refresh_token": "jwt-placeholder"
},
"certificateCache": {},
"certificateExpiry": null,
"certificateCacheFormat": "PEM",
"refreshTokenPromises": {},
"eagerRefreshThresholdMillis": 300000,
"forceRefreshOnFailure": false,
"email": "serviceaccount#appspot.gserviceaccount.com",
"key": "-----BEGIN PRIVATE KEY-----\nsomelongvalue=\n-----END PRIVATE KEY-----\n",
"scopes": [
"https://www.googleapis.com/auth/spreadsheets"
],
"subject": null,
"gtoken": {
"key": "-----BEGIN PRIVATE KEY-----\nsomelongvalue=\n-----END PRIVATE KEY-----\n",
"rawToken": {
"access_token": "somelongvalue...............",
"expires_in": 3599,
"token_type": "Bearer"
},
"iss": "serviceaccount#appspot.gserviceaccount.com",
"sub": null,
"scope": "https://www.googleapis.com/auth/spreadsheets",
"expiresAt": 1661662492000
}
}
ISSUE
Current response:
response:"401"
I cannot find any Google documentation on how to setup the headers to authenticate a request (from my service account) to my organisation restricted web app.
When the Web App is open to "Anyone" then it runs fine, but as soon as I restrict to MyOrganisation, I struggle to find a way to authenticate my POST request.
HELP!
How do I set up a POST request to my Google Sheets web app such that it can be protected by authentication? Right now, I'd be happy to find ANY means to authenticate this request (not necessarily a service account) that doesn't leave it completed open to public.
Should I use this hack?
One idea I had was to put a "secret" into my lambda function, and then make the web app public. The web app would check the secret, if if matched, would execute the function.
Modification points:
In order to access Web Apps using the access token with a script, the scopes of Drive API are required to be included. Those are https://www.googleapis.com/auth/drive.readonly, https://www.googleapis.com/auth/drive, and so on. Ref
When I saw your showing script, it seems that the access token is retrieved using google-spreadsheet. When I saw the script of google-spreadsheet, it seems that this uses only the scope of https://www.googleapis.com/auth/spreadsheets. Ref
From this situation, I thought that the reason for your current issue might be due to this. If my understanding is correct, how about the following modification? In this modification, the access token is retrieved by googleapis for Node.js from the service account. Ref
Modified script:
Google Apps Script side:
function doPost(e) {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss.getSheetByName("Sheet1");
sheet.getRange("A1").setValue("Hello!")
return ContentService.createTextOutput("Success!"); // Modified
}
When you modified the Google Apps Script, please modify the deployment as a new version. By this, the modified script is reflected in Web Apps. Please be careful about this.
You can see the detail of this in the report "Redeploying Web Apps without Changing URL of Web Apps for new IDE".
Node.js side:
const { google } = require("googleapis");
const HTTPS = require("https");
const auth = new google.auth.JWT(
"###", // Please set client_email here.
null,
"###", // Please set private_key here. When you set private_key of service account, please include \n.
["https://www.googleapis.com/auth/drive.readonly"],
null
);
function req(token) {
return new Promise((resolve, reject) => {
const data = { key1: "value1" }; // Please set your value.
const options = {
host: "script.google.com",
path: "/macros/s/{myscriptid}/exec", //<-- my web app path!
method: "POST",
headers: {Authorization: "Bearer " + token},
};
const req = HTTPS.request(options, (res) => {
if (res.statusCode == 302) {
HTTPS.get(res.headers.location, (res) => {
if (res.statusCode == 200) {
res.setEncoding("utf8");
res.on("data", (r) => resolve(r));
}
});
} else {
res.setEncoding("utf8");
res.on("data", (r) => resolve(r));
}
});
req.on("error", (e) => reject(e.message));
req.write(JSON.stringify(data));
req.end();
});
}
auth.getAccessToken().then(({ token }) => {
req(token).then((e) => console.log(e)).catch((e) => console.log(e));
});
When this script is run, when the Web Apps is correctly deployed, the script of Web Apps is run and Success! is returned.
Note:
If this modified script was not useful for your Web Apps setting, please test as follows.
Please confirm whether your service account can access to the Spreadsheet again.
Please share the email address of the service account on the Spreadsheet. From your showing Google Apps Script, I thought that your Google Apps Script is the container-bound script of the Spreadsheet.
Please reflect the latest script to the Web Apps.
When you modified the Google Apps Script, please modify the deployment as a new version. By this, the modified script is reflected in Web Apps. Please be careful about this.
You can see the detail of this in the report "Redeploying Web Apps without Changing URL of Web Apps for new IDE".
When you set private_key of service account, please include \n.
References:
Web Apps
Taking advantage of Web Apps with Google Apps Script
Added:
When you will directly put the value to the Spreadsheet using Sheets API with google-spreadsheet module, you can also use the following script.
const { GoogleSpreadsheet } = require("google-spreadsheet");
const sample = async () => {
const doc = new GoogleSpreadsheet("###"); // Please set your Spreadsheet ID.
await doc.useServiceAccountAuth({
client_email: client_email: process.env.GOOGLE_SERVICE_ACCOUNT_EMAIL,
private_key: process.env.GOOGLE_PRIVATE_KEY,
});
await doc.loadInfo();
const sheet = doc.sheetsByTitle["Sheet1"];
await sheet.loadCells("A1");
sheet.getCell(0, 0).value = "Hello!";
await sheet.saveUpdatedCells();
};
sample();
In this case, your service account is required to be able to access to the Spreadsheet. Please be careful about this.

403 on Google Action push notification request

I'm trying to perform a push notification for Google Actions Intent.
Thus far, I've followed the instructions here: https://developers.google.com/actions/assistant/updates/notifications#send_notifications
This is my resulting code:
const {google} = require('googleapis');
var request = require('request');
const key = require('./bot.json');
module.exports = async function (context, myQueueItem) {
context.log('JavaScript queue trigger function processed work item', myQueueItem);
let jwtClient = new google.auth.JWT(
key.client_email, null, key.private_key,
['https://www.googleapis.com/auth/actions.fulfillment.conversation'],
null
);
jwtClient.authorize((err, tokens) => {
// code to retrieve target userId and intent
let notif = {
userNotification: {
title: [message],
},
target: {
userId:[obtained from permission request],
intent: [name of intent],
// Expects a IETF BCP-47 language code (i.e. en-US)
locale: 'en-US'
},
};
request.post('https://actions.googleapis.com/v2/conversations:send', {
'auth': {
'bearer': tokens.access_token,
},
'json': true,
'body': {'customPushMessage': notif},
}, (err, httpResponse, body) => {
console.log(body);
console.log(httpResponse.statusCode + ': ' + httpResponse.statusMessage);
});
});
};
//module.exports(console, "Test");
This results in a 403 from the notification service. Is this because of the user id, intent name or jwtoken that was generated?
Following are the steps we need to check before sending the push notification
Check your Google permission settings
In order to test the Action, you need to enable the necessary permissions.
Go to the ‘Activity Controls' page (https://myaccount.google.com/activitycontrols).
Sign in with your Google account, if you have not already done so.
Ensure that the following permissions are enabled:
a.Web & App Activity
b.Device Information
c.Voice & Audio Activity
2.Target intent name should be added into the Implicit invocation field.
with enabled push notification.
3.use the same email id in your google assistant which you had used for login in GCP.

posting issues to Github repo via a Javascript ajax request

I am trying to create issues in a Github repo via a Javascript client. My code is like so
const github = `https://api.github.com/repos/${owner}/${repo}/issues`;
const msg = JSON.stringify({
"title": `problem with record id: ${recId}`,
"body": "something is wrong in Houston",
"assignee": "flooba",
"milestone": 1,
"labels": [
"images"
]
});
const x = new XMLHttpRequest();
x.open('POST', github, true);
x.onreadystatechange = function() {
if (x.readyState == 4) {
// show status message, all good
}
};
x.setRequestHeader("Content-type", "application/json");
x.setRequestHeader("Authorization", "Basic " + btoa("username:password")); //**
x.send(msg);
[**] username and password are the creds for the :repo, naturally.
When I try the above code, I get http 422. Suggestions?
Note: In the actual working code, I will put the credentials on the server side and invoke the server side process from the web client.

Only 2 intents work 'MAIN' and 'TEXT'

I'm trying building my first app with actions-on-google / google-assistant-sdk, I wanted to start using 3 intents, the MAIN, respond to input TEXT, and HELP that the user can call anytime:
The action.json is:
{
"actions": [
{
"description": "Default Welcome Intent",
"name": "MAIN",
"fulfillment": {
"conversationName": "conversation_1"
},
"intent": {
"name": "actions.intent.MAIN"
}
},
{
"description": "Help Intent",
"name": "Help",
"fulfillment": {
"conversationName": "conversation_1"
},
"intent": {
"name": "app.StandardIntents.HELP",
"trigger": {
"queryPatterns": [
"Help",
"HELP",
"help"
]
}
}
}
],
"conversations": {
"conversation_1": {
"name": "conversation_1",
"url": "https://us-central1-sillytest-16570.cloudfunctions.net/sayNumber",
"fulfillmentApiVersion": 2
}
}
}
The index.js:
'use strict';
process.env.DEBUG = 'actions-on-google:*';
const ActionsSdkApp = require('actions-on-google').ActionsSdkApp;
const functions = require('firebase-functions');
const NO_INPUTS = [
'I didn\'t hear that.',
'If you\'re still there, say that again.',
'We can stop here. See you soon.'
];
exports.sayNumber = functions.https.onRequest((request, response) => {
const app = new ActionsSdkApp({request, response});
function mainIntent (app) {
console.log('mainIntent');
let inputPrompt = app.buildInputPrompt(true, '<speak>Hi! <break time="1"/> ' +
'I can read out an ordinal like ' +
'<say-as interpret-as="ordinal">123</say-as>. Say a number.</speak>', NO_INPUTS);
app.ask(inputPrompt);
}
function rawInput (app) {
console.log('rawInput');
if (app.getRawInput() === 'bye') {
app.tell('Goodbye!');
} else {
let inputPrompt = app.buildInputPrompt(true, '<speak>You said, <say-as interpret-as="ordinal">' +
app.getRawInput() + '</say-as></speak>', NO_INPUTS);
app.ask(inputPrompt);
}
}
function helpHandler (app) {
console.log('rawInput');
app.ask('<speak>What kind of help do you need?</speak>');
}
let actionMap = new Map();
actionMap.set(app.StandardIntents.MAIN, mainIntent);
actionMap.set(app.StandardIntents.TEXT, rawInput);
actionMap.set(app.StandardIntents.HELP, helpHandler);
app.handleRequest(actionMap);
});
I pushed the firebase as:
firebase deploy --only functions
And pushed the Google Actions as:
gactions update --action_package action.json --project <YOUR_PROJECT_ID>
While testing the assistant here, it started in a good way, and repeat the number that I enter, wait for another number, and so on, but when I enter help it is terminated and not responding!
UPDATE
I tried the below, but did not work:
actionMap.set("app.StandardIntents.HELP", helpHandler);
I should expect the app to "What kind of help do you need?" when I enter/say "Help", but what happened is just re-writing it, same way it do with any other number.
Non-built-in Intents are only supported for the first message in a conversation. After that, while you can use them for speech biasing, you will only get a built-in one such as the TEXT Intent.
Your actionMap is looking for app.StandardIntents.HELP but it doesn't exist. You can view all of the standard intents in the GitHub repo.
app.StandardIntents.MAIN returns another string which corresponds to "'actions.intent.MAIN'". It does not read your action.json and generate new intents. Thus, app.StandardIntents.HELP actually returns undefined and is never called.
Your map should use a string for your help intent since it is not available as a constant in the app object.
actionMap.set("app.StandardIntents.HELP", helpHandler);
This should resolve your issue. Let me know if it does not.

how to get user id on facebook request dialog

What i want to do is get the request ID and insert to my database. My request dialog is run well, but the problem is I cannot get 'TO' user id http://developers.facebook.com/docs/reference/dialogs/requests/ , but I still cannot get the request user ID.
here is my coding:
function newInvite(){
//var user_ids = document.getElementsByName("user_ids")[0].value;
FB.ui({
method : 'apprequests',
title: 'X-MATCH',
message: 'Come join US now, having fun here',
},
function getMultipleRequests(requestIds) {
FB.api('', {"ids": requestIds }, function(response) {
console.log(response);
});
}
);
}
any solution on this?
million thanks for help
Have you enable request 2.0 efficient?
If you have enabled, you can get the user id easily as the response like this
{
request: ‘request_id’
to:[array of user_ids]
}
In your callback function, you can use
response.request to get the request ID
response.to to get the array of user ids
And notice that if you use request 2.0, the request ID format will like this
<request_object_id>_<user_id>
If you doesn't enable it, then you can only get the array of request ids and you need to make another api call to get the user id
Edited:
FB.ui({
method : "apprequests",
title : "your title",
message : "your msg"
},
function(response) {
var receiverIDs;
if (response.request) {
var receiverIDs = response.to; // receiverIDs is an array holding all user ids
}
}
);
Then you can use the array "receiverIDs" for further process
For example I sent a request to user id with id "1234", "5678"
The response will like this:
{
request: ‘1234567890’ // example,
to:['1234', '5678']
}
In request 2.0, the full request id will look like this
1234567890_1234
1234567890_5678
Caution: FB doc tell you to manage and delete the request yourself, if you using request 2.0,
remember to delete the id like the above, if you directly delete the request '123456789', all the full request ID with this prefix will be deleted.
==================================================================
If you haven't enable request 2.0, follow the code on the doc page to see how to get the user id by making a api call
function getMultipleRequests(requestIds) {
FB.api('', {"ids": requestIds }, function(response) {
console.log(response);
});
}
The response format for these methods is as follows:
{
"id": "[request_id]",
"application": {
"name": "[Application Name]",
"id": "[Application ID]"
},
"to": {
"name": "[Recipient User Name]",
"id": "[Recipient User ID]"
},
"from": {
"name": "[Sender User ID]",
"id": "[Sender User Name]"
},
"message": "[Request Message]",
"created_time": "2011-09-12T23:08:47+0000"
}
you can implement the callback of api call and getting who is the receiver by response.to.id