Using audio files as no input prompts in Google Actions - actions-on-google

I am trying to set up re-prompts in my Google Action, and I require them to be audio files.
This is my implementation:
'use strict';
const { dialogflow } = require("actions-on-google");
const functions = require("firebase-functions");
const app = dialogflow();
app.intent('Default Welcome Intent', (conv) => {
conv.noInputs = [`<speak> <audio src = "https://myurl.com/audio/myfile.mp3">My audio file</audio></speak>`];
console.log("Logging the conversation object... ");
console.log(JSON.stringify(conv));
conv.ask("Hello! ");
});
exports.yourAction = functions.https.onRequest(app);
However, at the moment it will just read the raw string of this noInputs array when I do a "no input" in the dev console!

Using this kind of static no-input handling is not suggested.
Better is to create an Intent that handles the actions_intent_NO_INPUT Event. You can then use the response section (or a response from your Fulfillment) to include SSML with the audio tag.

Related

How to close conversation from the webhook in #assistant/conversation

I want to close the conversation after the media started playing in #assistant/conversation. As I am doing here
app.intent("media", conv => {
conv.ask(`Playing your Radio`);
conv.ask(
new MediaObject({
url: ""
})
);
return conv.close(new Suggestions(`exit`));
});
As Jordi had mentioned, suggestion chips cannot be used to close a conversation. Additionally, the syntax of the #assistant/conversation is different from actions-on-google. As you're using the tag dialogflow-es-fulfillment but also actions-builder, I really don't know which answer you want. As such, I'm going to put two answers depending on which you're using.
Dialogflow
If you are using Dialogflow, you are pretty much set. You should switch to using actions-on-google and instantiate the dialogflow constant.
const {dialogflow} = require('actions-on-google')
const app = dialogflow()
Actions Builder
The syntax of the #assistant/conversation lib is different. Some method names are different. Additionally, you will need to go through Actions Builder to canonically close the conversation.
In your scene, you will need to transition the scene to End Conversation to close, rather than specifying it as part of your response. Still, your end transition should not have suggestion chips.
You will need to refactor your webhook:
const {conversation} = require('#assistant/conversation')
const app = conversation()
app.handle("media", conv => {
conv.add(`Playing your Radio`);
conv.add(
new MediaObject({
url: ""
})
);
conv.add(new Suggestions(`exit`));
});
As it seems you are trying to have a media control and after that to end the conversation, you should refer to the doc (https://developers.google.com/assistant/conversational/prompts-media) to check the available events as you have the chance to control each one for the media playback.
For example
// Media status
app.handle('media_status', (conv) => {
const mediaStatus = conv.intent.params.MEDIA_STATUS.resolved;
switch(mediaStatus) {
case 'FINISHED':
conv.add('Media has finished playing.');
break;
case 'FAILED':
conv.add('Media has failed.');
break;
case 'PAUSED' || 'STOPPED':
if (conv.request.context) {
// Persist the media progress value
const progress = conv.request.context.media.progress;
}
// Acknowledge pause/stop
conv.add(new Media({
mediaType: 'MEDIA_STATUS_ACK'
}));
break;
default:
conv.add('Unknown media status received.');
}
});
Once you get the FINISHED status you can offer the suggestion chip to exit the conversation.

Google Assistant make GET request and reply with server response

I'd like to create an action in Google Assistant such that when a voice command is issued, the Assistant will make a GET request to a URL, like http://example.com/response.txt and just read out the plaintext response. How do I go about doing that?
You would need to create an Action using Actions Builder or Dialogflow.
This Action would start with a 'Default Welcome Intent' that you should connect it to a webhook:
This webhook can be written simply using a language like Node.js
import {conversation} from '#assistant/conversation'
const fetch = require('node-fetch')
const app = conversation()
const URL = 'http://example.com/response.txt'
app.handle('Default Welcome Intent', async conv => {
const apiResponse = await fetch(URL)
const text = await apiResponse.text()
conv.add(text)
})
Depending on whether you just want static information or not, you may want to then add a transition to 'end conversation' to close it out.

Can I create follow-up actions on Actions on Google?

I know that I can deep link into my Google Home application by adding to my actions.json.
I also know that I can parse raw string values from the app.StandardIntents.TEXT intent that's provided by default, which I am currently doing like so:
if(app.getRawInput() === 'make payment') {
app.ask('Enter payment information: ');
}
else if(app.getRawInput() === 'quit') {
app.tell('Goodbye!');
}
But does Actions on Google provide direct support for creating follow-up intents, possibly after certain user voice inputs?
An example of a conversation flow is:
OK Google, talk to my app.
Welcome to my app, I can order your most recent purchase or your saved favorite. Which would you prefer?
Recent purchase.
Should I use your preferred address and method of payment?
Yes.
OK, I've placed your order.
My previous answer won't work after testing.
Here is a tested version.
exports.conversationComponent = functions.https.onRequest((req, res) => {
const app = new ApiAiApp({request: req, response: res});
console.log('Request headers: ' + JSON.stringify(req.headers));
console.log('Request body: ' + JSON.stringify(req.body));
const registerCallback = (app, funcName)=>{
if (!app.callbackMap.get(funcName)){
console.error(`Function ${funcName} required to be in app.callbackMap before calling registerCallback`);
return;
}
app.setContext("callback_followup", 1, {funcName});
}
const deRegisterCallback = (app)=>{
const context = app.getContext("callback_followup");
const funcName = app.getContextArgument("callback_followup", "funcName").value;
const func = app.callbackMap.get(funcName);
app.setContext("callback_followup", 0);
return func;
}
app.callbackMap = new Map();
app.callbackMap.set('endSurvey', (app)=>{
if (app.getUserConfirmation()) {
app.tell('Stopped, bye!');
}
else {
app.tell('Lets continue.');
}
});
app.callbackMap.set('confirmationStartSurvey', (app)=>{
const context = app.getContext("callback_follwup");
if (app.getUserConfirmation()) {
registerCallback(app, 'endSurvey');
app.askForConfirmation('Great! I\'m glad you want to do it!, do you want to stop?');
} else {
app.tell('That\'s okay. Let\'s not do it now.');
}
});
// Welcome
function welcome (app) {
registerCallback(app, 'confirmationStartSurvey');
const prompt = "You have one survey in your task list, do you want to proceed now?";
app.askForConfirmation(prompt);
}
function confirmationCalbackFollowup (app) {
const context = app.getContext("callback_followup");
if (! context){
console.error("ERROR: confirmationCallbackFollowup should always has context named callback_followup. ");
return;
}
const callback = deRegisterCallback(app);
return callback(app);
}
const actionMap = new Map();
actionMap.set(WELCOME, welcome);
actionMap.set('confirmation.callback.followup', confirmationCalbackFollowup );
app.handleRequest(actionMap);
});
The previous solution won't work because app is generated everytime the action function is called. I tried to save a callback function into app.data but it won't be existing next intent coming. So I changed another way. Register the callback function to app.callbackMap inside the function. so it will be there anyway.
To make it work, one important thing is Api.Ai need to have context defined in the intent. See the Api.Ai Intent here.
Make sure you have event, context, and action of course. otherwise, this intent won't be triggered.
Please let me know if you can use this solution. sorry for my previous wrong solution.
thanks
Can you give an example of a conversation flow that has what you are trying to do?
If you can use API.AI, they have Follow Up intents in the docs.
I do not think your code
if(app.getRawInput() === 'make payment') {
app.ask('Enter payment information: ');
}
else if(app.getRawInput() === 'quit') {
app.tell('Goodbye!');
}
is a good idea. I would suggest you have two different intent to handle "Payment information" and "Quit".

How to download mongo collections as file using iron-router (and ground-db)? [duplicate]

I'm playing with the idea of making a completely JavaScript-based zip/unzip utility that anyone can access from a browser. They can just drag their zip directly into the browser and it'll let them download all the files within. They can also create new zip files by dragging individual files in.
I know it'd be better to do it serverside, but this project is just for a bit of fun.
Dragging files into the browser should be easy enough if I take advantage of the various methods available. (Gmail style)
Encoding/decoding should hopefully be fine. I've seen some as3 zip libraries so I'm sure I should be fine with that.
My issue is downloading the files at the end.
window.location = 'data:jpg/image;base64,/9j/4AAQSkZJR....'
this works fine in Firefox but not in Chrome.
I can embed the files as images just fine in chrome using <img src="data:jpg/image;ba.." />, but the files won't necessarily be images. They could be any format.
Can anyone think of another solution or some kind of workaround?
If you also want to give a suggested name to the file (instead of the default 'download') you can use the following in Chrome, Firefox and some IE versions:
function downloadURI(uri, name) {
var link = document.createElement("a");
link.download = name;
link.href = uri;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
delete link;
}
And the following example shows it's use:
downloadURI("data:text/html,HelloWorld!", "helloWorld.txt");
function download(dataurl, filename) {
const link = document.createElement("a");
link.href = dataurl;
link.download = filename;
link.click();
}
download("data:text/html,HelloWorld!", "helloWorld.txt");
or:
function download(url, filename) {
fetch(url)
.then(response => response.blob())
.then(blob => {
const link = document.createElement("a");
link.href = URL.createObjectURL(blob);
link.download = filename;
link.click();
})
.catch(console.error);
}
download("https://get.geojs.io/v1/ip/geo.json","geoip.json")
download("data:text/html,HelloWorld!", "helloWorld.txt");
Ideas:
Try a <a href="data:...." target="_blank"> (Untested)
Use downloadify instead of data URLs (would work for IE as well)
Want to share my experience and help someone stuck on the downloads not working in Firefox and updated answer to 2014.
The below snippet will work in both firefox and chrome and it will accept a filename:
// Construct the <a> element
var link = document.createElement("a");
link.download = thefilename;
// Construct the uri
var uri = 'data:text/csv;charset=utf-8;base64,' + someb64data
link.href = uri;
document.body.appendChild(link);
link.click();
// Cleanup the DOM
document.body.removeChild(link);
Here is a pure JavaScript solution I tested working in Firefox and Chrome but not in Internet Explorer:
function downloadDataUrlFromJavascript(filename, dataUrl) {
// Construct the 'a' element
var link = document.createElement("a");
link.download = filename;
link.target = "_blank";
// Construct the URI
link.href = dataUrl;
document.body.appendChild(link);
link.click();
// Cleanup the DOM
document.body.removeChild(link);
delete link;
}
Cross-browser solutions found up until now:
downloadify -> Requires Flash
databounce -> Tested in IE 10 and 11, and doesn't work for me. Requires a servlet and some customization. (Incorrectly detects navigator. I had to set IE in compatibility mode to test, default charset in servlet, JavaScript options object with correct servlet path for absolute paths...) For non-IE browsers, it opens the file in the same window.
download.js -> http://danml.com/download.html Another library similar but not tested. Claims to be pure JavaScript, not requiring servlet nor Flash, but doesn't work on IE <= 9.
There are several solutions but they depend on HTML5 and haven't been implemented completely in some browsers yet. Examples below were tested in Chrome and Firefox (partly works).
Canvas example with save to file support. Just set your document.location.href to the data URI.
Anchor download example. It uses <a href="your-data-uri" download="filename.txt"> to specify file name.
Combining answers from #owencm and #Chazt3n, this function will allow download of text from IE11, Firefox, and Chrome. (Sorry, I don't have access to Safari or Opera, but please add a comment if you try and it works.)
initiate_user_download = function(file_name, mime_type, text) {
// Anything but IE works here
if (undefined === window.navigator.msSaveOrOpenBlob) {
var e = document.createElement('a');
var href = 'data:' + mime_type + ';charset=utf-8,' + encodeURIComponent(text);
e.setAttribute('href', href);
e.setAttribute('download', file_name);
document.body.appendChild(e);
e.click();
document.body.removeChild(e);
}
// IE-specific code
else {
var charCodeArr = new Array(text.length);
for (var i = 0; i < text.length; ++i) {
var charCode = text.charCodeAt(i);
charCodeArr[i] = charCode;
}
var blob = new Blob([new Uint8Array(charCodeArr)], {type: mime_type});
window.navigator.msSaveOrOpenBlob(blob, file_name);
}
}
// Example:
initiate_user_download('data.csv', 'text/csv', 'Sample,Data,Here\n1,2,3\n');
This can be solved 100% entirely with HTML alone. Just set the href attribute to "data:(mimetypeheader),(url)". For instance...
<a
href="data:video/mp4,http://www.example.com/video.mp4"
target="_blank"
download="video.mp4"
>Download Video</a>
Working example: JSFiddle Demo.
Because we use a Data URL, we are allowed to set the mimetype which indicates the type of data to download. Documentation:
Data URLs are composed of four parts: a prefix (data:), a MIME type indicating the type of data, an optional base64 token if non-textual, and the data itself. (Source: MDN Web Docs: Data URLs.)
Components:
<a ...> : The link tag.
href="data:video/mp4,http://www.example.com/video.mp4" : Here we are setting the link to the a data: with a header preconfigured to video/mp4. This is followed by the header mimetype. I.E., for a .txt file, it would would be text/plain. And then a comma separates it from the link we want to download.
target="_blank" : This indicates a new tab should be opened, it's not essential, but it helps guide the browser to the desired behavior.
download: This is the name of the file you're downloading.
If you only need to actually have a download action, like if you bind it to some button that will generate the URL on the fly when clicked (in Vue or React for example), you can do something as easy as this:
const link = document.createElement('a')
link.href = url
link.click()
In my case, the file is already properly named but you can set it thanks to filename if needed.
For anyone having issues in IE:
dataURItoBlob = function(dataURI) {
var binary = atob(dataURI.split(',')[1]);
var array = [];
for(var i = 0; i < binary.length; i++) {
array.push(binary.charCodeAt(i));
}
return new Blob([new Uint8Array(array)], {type: 'image/png'});
}
var blob = dataURItoBlob(uri);
window.navigator.msSaveOrOpenBlob(blob, "my-image.png");
This code was originally provided by #Yetti on this answer (separate question).
Your problem essentially boils down to "not all browsers will support this".
You could try a workaround and serve the unzipped files from a Flash object, but then you'd lose the JS-only purity (anyway, I'm not sure whether you currently can "drag files into browser" without some sort of Flash workaround - is that a HTML5 feature maybe?)
Coming late to the party, if you'd like to use a function without using the DOM, here it goes, since the DOM might not even be available for whatever reason.
It should be applicable in any Browser which has the fetch API.
Just test it here:
// declare the function
function downloadAsDataURL (url) {
return new Promise((resolve, reject) => {
fetch(url)
.then(res => res.blob())
.then(blob => {
const reader = new FileReader()
reader.readAsDataURL(blob)
reader.onloadend = () => resolve(reader.result)
reader.onerror = err => reject(err)
})
.catch(err => reject(err))
})
}
// simply use it like this
downloadAsDataURL ('https://cdn-icons-png.flaticon.com/512/3404/3404134.png')
.then((res) => {
console.log(res)
})
.catch((err) => {
console.error(err)
})
export const downloadAs = async (url: string, name: string) => {
const blob = await axios.get(url, {
headers: {
'Content-Type': 'application/octet-stream',
},
responseType: 'blob',
});
const a = document.createElement('a');
const href = window.URL.createObjectURL(blob.data);
a.href = href;
a.download = name;
a.click();
};
You can use a clean code solution, inform your url in a constant, and set it as param of open method instead in object window.
const url = "file url here"
window.open(url)

Firefox SDK block content-type

I am developing an add-on for Firefox and i want to block a special kind of requests in content-type.
For example i want to block the application/x-rar content-types and show a message in console.log
You are able to intercept the requests by observing the http-on-examine-response notification event and check getResponseHeader('Content-Type') for application/x-rar.
'use strict';
const {
Ci, Cr
} = require('chrome');
const events = require('sdk/system/events');
events.on('http-on-examine-response', function(event) {
let channel = event.subject.QueryInterface(Ci.nsIHttpChannel);
let contentType = channel.getResponseHeader('Content-Type');
if(contentType === 'applicaiton/x-rar'){
event.subject.cancel(Cr.NS_BINDING_ABORTED);
console.log('Aborted Request', channel.name);
}
});
Best of luck with developing your add-on.