How to build a simple app for Google Home with Dialogflow that returns a random item from an entities list? - actions-on-google

I am just starting to use Dialogflow to build some simple apps for my Google Home and I am having trouble creating an app that would simply returns a random name with a sentence.
For example: I say "give us a challenge": I want the app to return something like $random_name should do 10 push ups.
Is this possible to achieve?
Thank you!

As the comment above, you need to use your fulfillment to determine the name and reply it to the user. Simply, you can use like the code below to return the name from fixed name's array:
"use strict";
process.env.DEBUG = "actions-on-google:*";
const App = require("actions-on-google").DialogflowApp;
const request = require("request");
const nameListFromConst = [
"name1", "name2", "name3", "name4", "name5",
"name6", "name7", "name8", "name9", "name10"
];
exports.foobar = (req, res) => {
const app = new App({request: req, response: res});
const inputWelcome = app => {
const index = Math.floor(Math.random() * 10);
const name = nameListFromConst[index];
app.ask(name);
};
const actionMap = new Map();
actionMap.set("input.welcome", inputWelcome);
app.handleRequest(actionMap);
};
But, it seems that you want to determine the name from entities you registered into your agent of Dialogflow. If true, you can retrieve your entities with Dialogflow API dynamically in the fulfillment code like the following:
exports.foobar = (req, res) => {
const app = new App({request: req, response: res});
const inputWelcome = app => {
const options = {
url: "https://api.dialogflow.com/v1/entities/{YOUR_ENTITY_ID}",
method: "GET",
headers: {
"Authorization": "Bearer {YOUR_DEVELOPER_ACCESS_TOKEN}",
"Content-type": "application/json; charset=UTF-8"
},
json: true
};
request(options, (error, response, body) => {
if (error) {
console.log(error);
app.ask("Error occurred.");
} else {
const nameListFromEntity = body.entries.map(x => {
return x.value;
});
const index = Math.floor(Math.random() * 10);
const name = nameListFromEntity[index];
app.ask(name);
}
});
};
const actionMap = new Map();
actionMap.set("input.welcome", inputWelcome);
app.handleRequest(actionMap);
};

Related

Redeliver existing form submission to new webhook

I have set up a webhook between salesforce and Typeform and it's working fine. But Typeform has already filled form submissions. Now I want to deliver these responses to a new webhook is there a way to resync existing form submissions?
I dont think this is possible out of the box. You will need to fetch your responses via Typeform Responses API and feed them to your script or webhook.
It looks like the webhook payload is quite similar to the response returned by the API. You can write a script like this to feed all your existing responses from your typeform to a new webhook:
import fetch from 'node-fetch'
import crypto from 'crypto'
import { createClient } from '#typeform/api-client'
const token = process.env.TF_TOKEN // https://developer.typeform.com/get-started/personal-access-token/
const webhookSecret = process.env.SECRET
const uid = process.env.FORM_ID
const typeformAPI = createClient({ token })
const sleep = async (ms) => new Promise(res => setTimeout(res, ms))
// based on https://glitch.com/edit/#!/tf-webhook-receiver
const calculateSignature = (payload) => {
const hash = crypto
.createHmac('sha256', webhookSecret)
.update(payload)
.digest('base64')
return `sha256=${hash}`
}
const feedResponses = (before) => {
typeformAPI.responses.list({ uid, before }).then(async ({ items }) => {
if (items.length > 0) {
// process each response
for (let i=0; i<items.length; i+=1) {
const item = items[i]
const body = JSON.stringify({
"event_id": Date.now(),
"event_type": "form_response",
"form_response": item
})
const response = await fetch('/your-endpoint', {
method: 'POST',
headers: {
'Typeform-Signature': calculateSignature(body)
},
body,
})
const webhookResponse = await response.text()
console.log(webhookResponse)
await sleep(250) // rate-limit the requests
}
// continue with next page of responses
const { token } = items.at(-1)
feedResponses(token)
}
})
}
feedResponses()

Updating sub document using save() method in mongoose does not get saved in database and shows no error

I have a Mongoose model like this:
const centerSchema = mongoose.Schema({
centerName: {
type: String,
required: true,
},
candidates: [
{
candidateName: String,
voteReceived: {
type: Number,
default: 0,
},
candidateQR: {
type: String,
default: null,
},
},
],
totalVote: {
type: Number,
default: 0,
},
centerQR: String,
});
I have a Node.JS controller function like this:
exports.createCenter = async (req, res, next) => {
const newCenter = await Center.create(req.body);
newCenter.candidates.forEach(async (candidate, i) => {
const candidateQRGen = await promisify(qrCode.toDataURL)(
candidate._id.toString()
);
candidate.candidateQR = candidateQRGen;
// ** Tried these: **
// newCenter.markModified("candidates." + i);
// candidate.markModified("candidateQR");
});
// * Also tried this *
// newCenter.markModified("candidates");
const upDatedCenter = await newCenter.save();
res.status(201).json(upDatedCenter);
};
Simply, I want to modify the candidateQR field on the subdocument. The result should be like this:
{
"centerName": "Omuk Center",
"candidates": [
{
"candidateName": "A",
"voteReceived": 0,
"candidateQR": "some random qr code text",
"_id": "624433fc5bd40f70a4fda276"
},
{
"candidateName": "B",
"voteReceived": 0,
"candidateQR": "some random qr code text",
"_id": "624433fc5bd40f70a4fda277"
},
{
"candidateName": "C",
"voteReceived": 0,
"candidateQR": "some random qr code text",
"_id": "624433fc5bd40f70a4fda278"
}
],
"totalVote": 0,
"_id": "624433fc5bd40f70a4fda275",
"__v": 1,
}
But I am getting the candidateQR still as null in the Database. I tried markModified() method. But that didn't help (showed in the comment section in the code above). I didn't get any error message. In response I get the expected result. But that result is not being saved on the database. I just want candidateQR field to be changed. But couldn't figure out how.
forEach loop was the culprit here. After replacing the forEach with for...of it solved the issue. Basically, forEach takes a callback function which is marked as async in the codebase which returns a Promise initially and gets executed later.
As for...of doesn't take any callback function so the await inside of it falls under the controller function's scope and gets executed immediately. Thanks to Indraraj26 for pointing this out. So, the final working version of the controller would be like this:
exports.createCenter = async (req, res, next) => {
const newCenter = await Center.create(req.body);
for(const candidate of newCenter.candidates) {
const candidateQRGen = await promisify(qrCode.toDataURL)(
candidate._id.toString()
);
candidate.candidateQR = candidateQRGen;
};
newCenter.markModified("candidates");
const upDatedCenter = await newCenter.save();
res.status(201).json(upDatedCenter);
};
Also, shoutout to Moniruzzaman Dipto for showing a different approach to solve the issue using async.eachSeries() method.
You can use eachSeries instead of the forEach loop.
const async = require("async");
exports.createCenter = async (req, res, next) => {
const newCenter = await Center.create(req.body);
async.eachSeries(newCenter.candidates, async (candidate, done) => {
const candidateQRGen = await promisify(qrCode.toDataURL)(
candidate._id.toString(),
);
candidate.candidateQR = candidateQRGen;
newCenter.markModified("candidates");
await newCenter.save(done);
});
res.status(201).json(newCenter);
};
As far as I understand, you are just looping through the candidates array but you
are not storing the updated array. You need to store the updated data in a variable as well. Please give it a try with the solution below using map.
exports.createCenter = async (req, res, next) => {
const newCenter = await Center.create(req.body);
let candidates = newCenter.candidates;
candidates = candidates.map(candidate => {
const candidateQRGen = await promisify(qrCode.toDataURL)(
candidate._id.toString()
);
return {
...candidate,
candidateQR: candidateQRGen
}
});
newCenter.candidates = candidates;
const upDatedCenter = await newCenter.save();
res.status(201).json(upDatedCenter);
};
You can use this before save()
newCenter.markModified('candidates');

Axios sending url with params as string not object

i need to take url with params example:
https://domain.pl/ptpdf-gen?selected_posts=4871&advisor=magda,wojciech
But axios response is an object like:
{"https://domain.pl/ptpdf-gen?selected_posts":"4871","advisor":"magda,wojciech"}
How to send url as string via axios?
Optionally the request above could also be done as
axios.get('/user', {
params: {
selected_posts: 4871
advisor: ["magda", "Wojciech"]
},
paramsSerializer: params => {
return qs.stringify(params)
}
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
})
.then(function () {
// always executed
});
The qs is an external library,
https://www.npmjs.com/package/qs
var selected = 4871
var advisor = ["magda","wojciech"]
axios.post('https://domain.pl/ptpdf-gen', {selected, advisor })
So i made the url split like this, using URLSearchParams:
const currHref = window.location.search;
const urlParams = new URLSearchParams(window.location.search);
const myParam = urlParams.get('selected_posts');
const myParam2 = urlParams.get('advisor');
Then with axios.post i can send params:
axios.post("http://domain.pl/create", {myParam, myParam2})
On server i did handle params like:
const state = req.body;
const stateValues = Object.values(state);
And then i can concat url with stateValues[0] and stateValues[1];
let linkUrl = "https://domain.pl/ptpdf-gen?selected_posts=" + stateValues[0] + "&advisor=" + stateValues[1];
Works.

Agora Cloud Recording: Error 404 (Flutter + Google Cloud Function)

Currently having issues starting cloud recording for an Agora stream.
I'm using flutter and created a cloud function to start the recording.
Flutter:
AgoraRtcEngine.onJoinChannelSuccess = (String channel, int uid, int elapsed) async {
AgoraCloudRecording().startAgoraCloudRecording(channel, uid);
};
Cloud Function:
export async function retrieveAgoraToken(data: any){
//Variables
const channel = data.channel;
const uid = data.uid;
//AGORA KEYS
const agoraDoc = await agoraDocRef.get();
const appID = agoraDoc.data()!.appID;
const customerID = agoraDoc.data()!.customerID;
const secret = agoraDoc.data()!.secret;
const agoraCredentials = Base64.encode(customerID + ":" + secret);
//AWS
const awsDoc = await awsDocRef.get();
const awsAccessKey = awsDoc.data()!.accessKey;
const awsSecretKey = awsDoc.data()!.secretKey;
const reqHeaders = {"Authorization": "Basic" + agoraCredentials, "Content-type": "application/json"};
const acquireReqBody = {
"cname": channel,
"uid": "1",
"clientRequest": {
"resourceExpiredHour": 24
}
};
console.log(reqHeaders);
//GET RESOURCE ID
const acquireURL = 'https://api.agora.io/v1/apps/' + appID + '/cloud_recording/acquire';
const acquireResourceID = {
method: 'POST',
url: encodeURI(acquireURL),
headers: reqHeaders,
body: JSON.stringify(acquireReqBody)
}
const resourceIDRequest = new Promise<any>((resolve, reject) => {
request(acquireResourceID, function (error:any, res:any, body:any) {
console.error('error:', error); // Print the error if one occurred
console.log('statusCode:', res && res.statusCode); // Print the response status code if a response was received
resolve(JSON.parse(body));
reject('error');
});
});
const resourceIDResponse = await resourceIDRequest;
console.log(resourceIDResponse);
const resourceID = resourceIDResponse.resourceId;
console.log(resourceID);
//START RECORDING
const recordingReqBody = {
"cname": channel,
"uid": "1",
"clientRequest": {
"recordingConfig": {
"maxIdleTime": 30,
"streamTypes": 2,
"channelType": 1,
"videoStreamType": 0,
"transcodingConfig": {
"height": 640,
"width": 360,
"bitrate": 500,
"fps": 15,
"mixedVideoLayout": 1,
"backgroundColor": "#FF0000"
},
"subscribeVideoUids": [
uid
],
"subscribeAudioUids": [
uid
],
"subscribeUidGroup": 0
},
"recordingFileConfig": {
"avFileType": [
"hls"
]
},
"storageConfig": {
"accessKey": awsAccessKey,
"region": 1,
"bucket": "recorded-live-streams",
"secretKey": awsSecretKey,
"vendor": 1,
"fileNamePrefix": [
"directory1",
"directory2"
]
}
}
};
const startRecordingURL = encodeURI('https://api.agora.io/v1​/apps​/' + appID + '​/cloud_recording​/resourceid​/' + resourceID + '​/mode​/mix/start');
console.log(startRecordingURL);
const startRecordingOptions = {
method: 'POST',
url: startRecordingURL,
headers: reqHeaders,
body: JSON.stringify(recordingReqBody)
}
const startVideoRecordingReq = new Promise<any>((resolve, reject) => {
request(startRecordingOptions, function (error:any, res:any, body:any) {
console.error('error:', error); // Print the error if one occurred
console.log('statusCode:', res && res.statusCode); // Print the response status code if a response was received
resolve(JSON.parse(body));
reject('error');
});
});
const startVidRecordingResponse = await startVideoRecordingReq;
console.log(startVidRecordingResponse);
}
I've even tried to start recording adding token parameters.
I would generate the token via NodeJS with this library: https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/nodejs
I can successfully aquire the resourceID from Agora. However, if I try to start recording, I get a 404 error: 'no Route matched with those values'
I have found a document here that provides some context to the error you reported.
"no Route matched with those values": A possible reason for this message is that you entered an incorrect HTTP method in your request, such as using GET when it should have been POST. Another possible reason is that you sent your request to a wrong URL.
Please make sure to enable Cloud Recording on your project through the Agora Console.
To enable Cloud Recording on your project, visit https://console.agora.io/cloud-recording and select the Project name from the drop-down in the upper left-hand corner, click the Duration link below Cloud Recording and click the button to enable the service on that project.
After you click Enable Cloud Recording, you will be prompted to confirm the concurrent channels settings which defaults to 50, click OK. Once enabled, you should see the usage graph enabled.

How to translate superagent to axios?

I have some upload working for superagent. It involves posting to an api for cloudinary. My question is how do I do the same thing with axios. I'm not sure what superagent.attach and superagent.field relate to in axios.
Basically when I make the post request I need to attach all these fields to the request or else I get bad request and I want to do this in axios not superagent as I am switching over to axios.
Here are all the params:
const image = files[0];
const cloudName = 'tbaustin';
const url = `https://api.cloudinary.com/v1_1/${cloudName}/image/upload`;
const timestamp = Date.now()/1000;
const uploadPreset = 'cnh7rzwp';
const paramsStr = `timestamp=${timestamp}&upload_preset=${uploadPreset}ORor-6scjYwQGpNBvMW2HGMkc8k`;
const signature = sha1(paramsStr);
const params = {
'api_key': '177287448318217',
'timestamp': timestamp,
'upload_preset': uploadPreset,
'signature': signature
}
Here is the superagent post request:
let uploadRequest = superagent.post(url)
uploadRequest.attach('file', image);
Object.keys(params).forEach((key) => {
uploadRequest.field(key, params[key]);
});
uploadRequest.end((err, res) => {
if(err) {
alert(err);
return
}
You would need to use FromData as follows:
var url = `https://api.cloudinary.com/v1_1/${cloudName}/upload`;
var fd = new FormData();
fd.append("upload_preset", unsignedUploadPreset);
fd.append("tags", "browser_upload"); // Optional - add tag for image admin in Cloudinary
fd.append("signature", signature);
fd.append("file", file);
const config = {
headers: { "X-Requested-With": "XMLHttpRequest" },
onUploadProgress: function(progressEvent) {
// Do something with the native progress event
}
};
axios.post(url, fd, config)
.then(function (res) {
// File uploaded successfully
console.log(res.data);
})
.catch(function (err) {
console.error('err', err);
});
See full example here