Can I create follow-up actions on Actions on Google? - 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".

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.

Protractor: How do I create a function that receives an HTML element as a parameter?

I'm new to Protractor.
I'm trying to select a button based on the button title. I want to make this into a function, and pass the button title in as a parameter.
This is the hard-coded version which works:
it('I click on a button based on the button title', async function() {
let button = element(by.css('button[title=example_button_title]'));
await button.click();
});
I created a global variable and a function to try and replace this, where 'buttonTitle' is the parameter I'm passing into the function:
Variable:
let dynamicButton = buttonTitle => { return element(by.css("'button[title=" + buttonTitle + "]'")) };
Function:
this.selectDynamicButton = async function(buttonTitle) {
await browser.waitForAngularEnabled(false);
await dynamicButton(buttonTitle).click();
};
When I try this I get the following error:
Failed: invalid selector: An invalid or illegal selector was specified
Apologies if there appear to be basic errors here, I am still learning. I appreciate any help that anyone can give me. Thanks.
You can add a custom locator using protractors addLocator functionality. (this is actually a very similar use case to the example listed in the link)
This would look like the following:
onPrepare: function () {
by.addLocator('buttonTitle', function (titleText, opt_parentElement) {
// This function will be serialized as a string and will execute in the
// browser. The first argument is the text for the button. The second
// argument is the parent element, if any.
const using = opt_parentElement || document;
const matchingButtons = using.querySelectorAll(`button[title="${titleText}"]`);
let result = undefined;
if (matchingButtons.length === 0) {
result = null;
} else if (matchingButtons.length === 1) {
result = matchingButtons[0];
} else {
result = matchingButtons;
}
return result;
});
}
This is called like
const firstMatchingButton = element(by.buttonTitle('example_button_title'));
const allMatchingButtons = element.all(by.buttonTitle('example_button_title'));
I had to edit this code before posting so let me know if this does not work. My work here is largely based off this previous answer
let dynamicButton = buttonTitle => { return element(by.css('button[title=${buttonTitle} ]')) };
Use template literals instead of string concatenation with +.
Protractor already has a built in locator which allows you to get a button using the text. I think you are looking at something like that. See the element(by.buttonText('text of button')) locator.
For more reference see here.

How do I add a "save" button to the gtk filechooser dialog?

I have a Gjs app that will need to save files. I can open the file chooser dialog just fine from my menu, and I have added a "save" and "cancel" button, but I can't get the "save" button to trigger anything.
I know I'm supposed to pass it a response_id, but I'm not sure what that's supposed to look like nor what I'm supposed to do with it afterwards.
I read that part here:
https://www.roojs.com/seed/gir-1.2-gtk-3.0/gjs/Gtk.FileChooserDialog.html#expand
let actionSaveAs = new Gio.SimpleAction ({ name: 'saveAs' });
actionSaveAs.connect('activate', () => {
const saver = new Gtk.FileChooserDialog({title:'Select a destination'});
saver.set_action(Gtk.FileChooserAction.SAVE);
saver.add_button('save', 'GTK_RESPONSE_ACCEPT');
saver.add_button('cancel', 'GTK_RESPONSE_CANCEL');
const res = saver.run();
if (res) {
print(res);
const filename = saver.get_filename();
print(filename);
}
saver.destroy();
});
APP.add_action(actionSaveAs);
I can catch res and fire the associated little logging action when I close the dialog, but both the "save" and "cancel" buttons just close the dialog without doing or saying anything.
My question is, what are GTK_RESPONSE_ACCEPT and GTK_RESPONSE_CANCEL supposed to be (look like) in GJS and how do I use them?
In GJS enums like GTK_RESPONSE_* are numbers and effectively look like this:
// imagine this is the Gtk import
const Gtk = {
ResponseType: {
NONE: -1,
REJECT: -2,
ACCEPT: -3,
DELETE_EVENT: -4,
...
}
};
// access like so
let response_id = -3;
if (response_id === Gtk.ResponseType.ACCEPT) {
log(true);
}
There's a bit more information here about that.
let saver = new Gtk.FileChooserDialog({
title:'Select a destination',
// you had the enum usage correct here
action: Gtk.FileChooserAction.SAVE
});
// Really the response code doesn't matter much, since you're
// deciding what to do with it. You could pass number literals
// like 1, 2 or 3. Probably this was not working because you were
// passing a string as a response id.
saver.add_button('Cancel', Gtk.ResponseType.CANCEL);
saver.add_button('Save', Gtk.ResponseType.OK);
// run() is handy, but be aware that it will block the current (only)
// thread until it returns, so I usually prefer to connect to the
// GtkDialog::response signal and use GtkWidget.show()
saver.connect('response', (dialog, response_id) => {
if (response_id === Gtk.ResponseType.OK) {
// outputs "-5"
print(response_id);
// NOTE: we're using #dialog instead of 'saver' in the callback to
// avoid a possible cyclic reference which could prevent the dialog
// from being garbage collected.
let filename = dialog.get_filename();
// here's where you do your stuff with the filename. You might consider
// wrapping this whole thing in a re-usable Promise. Then you could call
// `resolve(filename)` or maybe `resolve(null)` if the response_id
// was not Gtk.ResponseType.OK. You could then `await` the result to get
// the same functionality as run() but allow other code to execute while
// you wait for the user.
print(filename);
// Also note, you actually have to do the writing yourself, such as
// with a GFile. GtkFileChooserDialog is really just for getting a
// file path from the user
let file = Gio.File.new_for_path(filename);
file.replace_contents_bytes_async(
// of course you actually need bytes to write, since GActions
// have no way to return a value, unless you're passing all the
// data through as a parameter, it might not be the best option
new GLib.Bytes('file contents to write to disk'),
null,
false,
Gio.FileCreateFlags.REPLACE_DESTINATION,
null,
// "shadowing" variable with the same name is another way
// to prevent cyclic references in callbacks.
(file, res) => {
try {
file.replace_contents_finish(res);
} catch (e) {
logError(e);
}
}
);
}
// destroy the dialog regardless of the response when we're done.
dialog.destroy();
});
// for bonus points, here's how you'd implement a simple preview widget ;)
saver.preview_widget = new Gtk.Image();
saver.preview_widget_active = false;
this.connect('update-preview', (dialog) => {
try {
// you'll have to import GdkPixbuf to use this
let pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(
dialog.get_preview_filename(),
dialog.get_scale_factor() * 128,
-1
);
dialog.preview_widget.pixbuf = pixbuf;
dialog.preview_widget.visible = true;
dialog.preview_widget_active = true;
// if there's some kind of error or the file isn't an image
// we'll just hide the preview widget
} catch (e) {
dialog.preview_widget.visible = false;
dialog.preview_widget_active = false;
}
});
// this is how we'll show the dialog to the user
saver.show();

In protractor, I want the code to handle based on if OTP triggers and if not, I can login to the home page or any page and cont do the work

I am new to coding and as well as to protractor.
In protractor, I want the code to handle based on if OTP triggers go and retrieve OTP and if not, login to the home page or any page and continue to do the actions in the home page. I was trying to do an if else check with
I tried as like below
browser.getcurrentUrl().toEqual().then function()
{
statements;
},
I don't think it works. Can someone help?
below is my code snippet.
Basically i was trying to check the url, if it contains specific texts in it, I dont want anything to perform further execution want to exit out of execution. If the url doesnt contain anything specified I want to proceed with further execution.
The if condition is working fine. but not the else part.
var HomePages = require('../Pages/HomePage.js');
var EC = protractor.ExpectedConditions;
describe(‘Check_url function’, function() {
browser.wait(EC.urlContains(’some url’),2000).then(result => {
if (result) {
console.log('Sorry!!!!!!!, Encountered PassCode Authentication Process.
Execution cant be proceed further');
} else {
HomePages.profile();
browser.driver.sleep(300);
}
});
});
//////////////////////////
HomePages.js -
'use strict';
module.exports = {
Homepage: {
usrname: element(by.className('profile-name')),
usricon: element(by.css('[title="profile"]')),
Cli_id: element(by.css('[title=“Client ID"]'))
},
profile: function() {
this.click_Profile();
},
click_Profile: function() {
var angular3 = this.Homepage;
angular3.usricon.click();
},

Unfriending someone through the Facebook API?

Is it possible to remove a friend relationship between two FB users through the API? I'm thinking that it's not, but (if not) is it possible to at least bring up a dialog that would let the user request the unfriending, similar to how the Friends Dialog (http://developers.facebook.com/docs/reference/dialogs/friends/) lets a user send a friend invitation?
It is not possible through the API. Facebook is like the mafia - you can get in. but there's no way out.
Simialar to this question:
Any way to unfriend or delete a friend using Facebook's PHP SDK or API?
Also, it is against the terms of service for facebook apps to ask people to unfriend. There was a BurgerKing prootional app that famously ran afoul of that after going viral.
http://www.insidefacebook.com/2009/01/14/whopper-sacrifice-shut-down-by-facebook/
Let friends unfriend on their own time.
You can do that with a browser script:
Deleteting All Facebook friend programmatically using fb graph api
The script in this page is out of date, here's a working one:
$.ajax({
url: "https://graph.facebook.com/me/friends?access_token=ACCESS_TOKEN", // get this at https://developers.facebook.com/tools/explorer take the Friends link and replace it.
success: function(data) {
jQuery.each(data.data, function() {
$.ajax({
url: "https://m.facebook.com/a/removefriend.php",
data: "friend_id="+this.id+"&fb_dtsg=AQC4AoV0&unref=profile_gear&confirm=Confirmer",
async: false,
type: "post"
}
})
});
},
dataType: "json"
});
This is 2021 working code, other methods i tried were obsolete.
Go to https://m.facebook.com/friends/center/friends and open browser console.
Execute jquery-min defintion code from https://code.jquery.com/jquery-3.6.0.min.js into browser console.
Run the following code from your brower console.
// from https://m.facebook.com/friends/center/friends
// first copy paste: https://code.jquery.com/jquery-3.6.0.min.js
let ok = this.document
let firstrec = ok.firstChild.nextSibling.firstChild.nextElementSibling.firstChild.nextElementSibling.firstChild.nextElementSibling.firstChild.nextElementSibling.nextElementSibling.nextElementSibling.firstElementChild.firstElementChild.firstElementChild.nextElementSibling.firstElementChild nextrec = firstrec
// simulate click function async function clickf(div, entry) {
if (entry == null) {
await $(div).click()
return await $(div).click()
}
if (entry == "0") {
console.log("ehhe")
await $(div)[0].click()browse
return await $(div)[0].click()
} }
function* removefb() {
while (true) {
nextclick = nextrec.firstElementChild.nextElementSibling.nextElementSibling.firstElementChild.firstElementChild.firstElementChild.nextElementSibling.nextElementSibling.nextElementSibling.firstElementChild
nextreccopy = nextrec
nextrec = nextrec.nextSibling
if (nextrec == null) {
nextrec = nextreccopy
nextrec = nextrec.parentElement.nextElementSibling.firstElementChild
}
clickf(nextclick)
remover = nextclick.nextElementSibling.firstElementChild.firstElementChild.firstElementChild.nextElementSibling.firstElementChild.nextElementSibling
clickf(remover, 0)
yield
} }
function greet() {
removefb().next() }
setInterval(greet, 1000);
https://github.com/danass/remove-fb-friends/
This code help you to mass remove all facebook friends.