Make next input message part of callback_query function in Telegram Bot - bots

I'm programming a Telegram Bot where it is shown to the user different options as inline buttons, one of them being "Search".
Once in search, I intend the next sent message to be the string to be used to search. However, I don't know how to make it so that the sent message is for the search query and not for other option/purposes.
If the bot file is read again from start every time a message or button is pressed, there is no way I can tell the bot that the next message is a message related to the pressed button (search) or a standard message not related to the pressed button (I don't know if I'm clear enough with my doubt or I'm making it more complicated to understand).
So relevant part of the code looks like this:
if(isset($request->callback_query->data)){
$data = $request->callback_query->data;
switch($data){
case 'search':
sendMessage($request->callback_query->message->chat->id, "Write what you want to search.");
//(HERE is where the program should "wait" for the user to input something to be used in a DB query)
//DBquery function
break;
}
}else{
keyboard($request->message->chat->id, "Choose one of the following options:", $keyboard);
}
$keyboard = array(
"inline_keyboard" => [
[
["text" => "\xF0\x9F\x94\x8E"." Search item","callback_data" => "search"],
["text" => "\xF0\x9F\x8C\x8F"." Visit web","url" => "https://website.com"]
//more options to be written
]
]
);

Related

Cant trigger message after reloadaction

After I enter "reset", I does not received any message "Welcome Back" and restart the conversation.
bot.dialog('reset', [
function(session) {
session.send("Welcome Back");
session.beginDialog('/');
}]
)
.reloadAction('reset', 'Ok, starting over.', {
matches: /^reset/i,
});
The reloadAction only works for dialogs that have already been added to the stack. In order to use it correctly, the reloadAction needs to be attached to a dialog that has already been called/used previously.
For example, if a user is responding to a series of user profile questions across multiple dialogs (personal info, then address/previous address, followed by education) and the user types 'reset' while in the education dialog, then the reloadAction would trigger having been attached to the first user profile dialog (that collects personal info). Thus, the user would be brought back to the beginning of the user profile dialogs to start over.
If you are intent on calling a new dialog, then you will likely want to use triggerAction. This does clear the dialog stack entirely but also allows you to redirect back to dialog x. In this way, you achieve the same result as in your ask with just a little bit of extra code:
bot.dialog('reset', [
function(session, args, next) {
session.send("Ok, starting over.");
next();
},
function(session) {
session.send("Welcome Back");
session.beginDialog('/');
}]
).triggerAction({
matches: /^reset/i
});
Hope of help!

dynamic prompt choices in bot-framework v4 (node.js)

I've got a prompt for an SMS bot in which the user can make multiple choices. I'm looking for a pattern for a ChoicePrompt that allows me to do this:
show multiple selections
then after the user selects and answer, re-prompt them to answer again
Remove their previous choice(s) and add an "exit" option to move on
Automatically end the step if they've selected everything.
I'd like to avoid creating a new prompt w/switch cases for each answer tier, as this pattern needs to be implemented in a lot of places...
Example:
bot: User, what do you do to relax?
Exercise
Read a book
Nothing
user: Exercise
bot: Exercise, cool. What else?
Read a book
Nothing else
user: Read a book
bot: OK, you've done everything so we're moving on!
The botframework don't have a ListPrompt that I can see, at least for v4. They do however, have Suggested Actions you can use for this!!! The Botbuilder-Samples repo has a Suggested Action sample that shows a list of three colors:
async onTurn(turnContext) {
// See https://aka.ms/about-bot-activity-message to learn more about the message and other activity types.
if (turnContext.activity.type === ActivityTypes.Message) {
const text = turnContext.activity.text;
// Create an array with the valid color options.
const validColors = ['Red', 'Blue', 'Yellow'];
// If the `text` is in the Array, a valid color was selected and send agreement.
if (validColors.includes(text)) {
await turnContext.sendActivity(`I agree, ${ text } is the best color.`);
} else {
await turnContext.sendActivity('Please select a color.');
}
// After the bot has responded send the suggested actions.
await this.sendSuggestedActions(turnContext);
} else if (turnContext.activity.type === ActivityTypes.ConversationUpdate) {
await this.sendWelcomeMessage(turnContext);
} else {
await turnContext.sendActivity(`[${ turnContext.activity.type } event detected.]`);
}
}
An option would be to programatically create the array (in the example above, it's "const validColors") and if the reply is in the list of colors, recreate the array however you want without the chosen option.

botframework choice invalid response typed

I am using nodejs SDK for creating my bot with MSFT botframework.
The code snippet is as follows:
function(session, args, next){
builder.Prompts.choice(session, "Please select one of the options:", ['AAA', 'BBB','CCC'], {retryPrompt: "Invalid choice, Please pick below listed choices",
listStyle: builder.ListStyle.button,
maxRetries: 1
});
},
function(session,results){
if (results.response) {
//Do something
}
}
I have 2 questions:
I would like to navigate to a different dialog Flow in case the user types anything other then the options("AAA","BBB","CCC"). Is that possible?
I would like to change the retryPrompt everytime lets say pick the utterances from a list. Is that possible?
I would like to navigate to a different dialog Flow in case the user types anything other then the options("AAA","BBB","CCC"). Is that possible?
Yes, it's possible. You can define several dialogs with required waterfall steps related to the choices. Like:
bot.dialog('AAA',[...])
And leverage replaceDialog to redirect your user to new dialog flows:
(session,result)=>{
//result.response.entity should be one of string `AAA`,`BBB`,`CCC`
session.replaceDialog(result.response.entity);
}
I would like to change the retryPrompt everytime lets say pick the utterances from a list. Is that possible?
Yes, it's possible. According the choice definition, we can set options extends IPromptChoiceOptions which extends [IPromptOptions][2], we can find that retryPrompt?: TextOrMessageType;, dig into the source code for the definition of TextOrMessageType:
/**
* Flexible range of possible prompts that can be sent to a user.
* * _{string}_ - A simple message to send the user.
* * _{string[]}_ - Array of possible messages to send the user. One will be chosen at random.
...
...
*/
export type TextOrMessageType = string|string[]|IMessage|IIsMessage;
So we can set a string list for retryPrompt, bot builder will pick one randomly. Please try following:
builder.Prompts.choice(session, "Please select one of the options:", ['AAA', 'BBB', 'CCC'], {
listStyle: builder.ListStyle.button,
maxRetries: 1,
retryPrompt:['utterance 1','utterance 2','utterance 3','utterance 4']
});
As you can call a function in retryPrompt you can do both of them:
builder.Prompts.choice(
session,
'This is just a question?',
'Yes|No',
{ retryPrompt: particularRetry() }
);
In the above, you can do what you want in particularRetry function.
For example for the second question, you can call the new propmt with new choices.
You can see more details in this post.

Multiple buttons in HeroCard

I'd like to have multiple buttons on HeroCard
and be able to press all buttons one after another
but when I press click button program jumps to next function in waterfall
and expects next action instead of button action again
what should I do in this case?
bot.dialog("/showCards", [
(session) => {
const msg = new Message(session)
.textFormat(TextFormat.xml)
.attachmentLayout(AttachmentLayout.carousel)
.attachments([{
title: "title",
url: "https://www.wikipedia.org/portal/wikipedia.org/assets/img/Wikipedia-logo-v2.png"
}].map(obj =>
new HeroCard(session)
.title(obj.title)
.images([
CardImage.create(session, obj.url)
.tap(CardAction.showImage(session, obj.url)),
])
.buttons([
CardAction.openUrl(session, obj.url),
CardAction.imBack(session, `click`, "Click"),
CardAction.imBack(session, `clack`, "Clack")
])
));
Prompts.choice(session, msg, ["click", "clack"]);
},
(session, results) => {
// todo use results.response.entity
}
]);
You could also use CardAction.dialogAction and link every button to a beginDialogAction.
let card = new builder.HeroCard(session)
.title(title)
.subtitle(subtitle)
.buttons([builder.CardAction.dialogAction(session, 'dialogAAction', 'dataYouNeedInDialogA', 'ButtonTitleA'), builder.CardAction.dialogAction(session, 'dialogBAction', 'dataYouNeedInDialogA', 'ButtonTitleB')]);
let msg = new builder.Message(session)
.attachments([card])
session.endDialog(msg);
// use one of these two to either end the dialog and start a new one or to stay in the current dialog and wait for user input
session.send(msg);
// don't forget to add the dialogs to your bot / library later in your code (outside your current dialog)
bot.dialog('dialogA', dialogA); // initialized somewhere in your code
bot.dialog('dialogB', dialogB);
bot.beginDialogAction('dialogAAction', 'dialogA');
bot.beginDialogAction('dialogBAction', 'dialogB', {
onSelectAction: (session, args, next) => {
// you might want to clear the dialogStack if the button is pressed. Otherwise, if the button is pressed multiple times, instances of dialogB are pilled up on the dialog stack.
session.clearDialogStack();
next();
}
});
In my opinion, this is the best way to achieve the behaviour you described so far. All buttons work whenever the user presses them, even if they scroll back in the conversation and press the same button again. The only trade-off is that you have to pass data to the new dialog and can not use dialogData throughout the whole flow. Nevertheless, I think it's worth it because ensures consistent UX throughout the usage of the bot.
Hope this helps. You can build click and clack dialogs, link them to actions and pass the data that you need. The user would be able to press click, clack, click and the bot would still work. :)
Use a switch-case in the ResumeAfter function, in the default case send the user to the previous function.

How to add additional dialog in bot framework

How can I have 2 conversations going concurrently? I'm currently using TextBot and LuisDialog to build a bot. I start off by having a conversation with the user to obtain data. Then while doing some processing in a different method, I discover that I need additional information from the user. How can I create a new conversation with the user just to get that additional information? I have some code below that attempts to show what I want to do. Thanks for your suggestions.
File 1: foo.js
var dialog = new builder.LuisDialog(model);
var sonnyBot = new builder.TextBot();
sonnyBot.add('/', dialog);
dialog.on('intent_1', [
function(session, args, next) {
name = builder.Prompts.text(session,"What is your name?");
},
function(session, result) {
session.dialogData.name= results.response;
getFamilyTree(session.dialogData.name);
}
]);
File 2: getFamilyTree.js
function getFamilyTree(name) {
find family tree for name
if (need place of birth) {
begin new dialog
prompt user place of birth
get place of birth from user
end dialog
}
finish getting the family tree
}
i guess you could pass session object and then use that object to start a new dialog .
Edit 1
can't you use some thing like
session.beginDialog('/getFamilyTree',{name:result.response});
and then you can access name like
args.name
inside 'getFamilyTree' dialog
I posted the same question on GitHub and received the answer from Steven Ickman, who is involved in the development of the node.js SDK. The link to the answer is https://github.com/Microsoft/BotBuilder/issues/394#issuecomment-223127365

Resources