Retry prompt customization - node.js

I am using MS bot builder node.js SDK. Before one of the recent updates, when the prompt is retried, it was sending the same message text to the user as the retry prompt.
However, now it is sending the default text message in the system, which is "I didn't understand.Please try again". However, I want retry prompts always be the same as the original message and if possible want to apply this globally, meaning I don't want to customize retry prompt for every prompt I am sending to the user.
I had been looking around, but couldn't find a way yet.
Thanks!

You can modify the prompts to automatically set the prompt as the retry prompt. The Prompts interface shows how the args are passed in to the base Prompt classes, so we can modify this prompt behavior by accessing the method in Prompts.
Here's an example of how to do it with Prompts.confirm
const promptPrefix = 'BotBuilder:prompt-';
bot.dialog('/', [
(session) => {
builder.Prompts.confirm(session, 'Say yes or no');
},
(session, args) => {
session.endConversation('You said: ' + session.message.text);
}
]);
builder.Prompts.confirm = (session, prompt, options) => {
var args = options || {};
args.prompt = prompt || args.prompt;
// If options.retryPrompt was passed in use this, otherwise use prompt
args.retryPrompt = args.retryPrompt || args.prompt;
session.beginDialog(promptPrefix + 'confirm', args);
};
The modified Prompts.confirm in action:

One option is to send the retry prompt as an option to the prompt. For example:
builder.Prompts.number(session, "What's the number?", {
retryPrompt: "What's the number?"
});
But you will have to configure that on every prompt.

Related

Edit variables with command discord.js v13

I'm working on a discord.js v13 bot, and I want to make a command that add the channel Id where the command executed in an existing variable and, another command that works only with the channels in the variable
This is the variable
const ch = ["1068584484473143386","1068570010756337705","","","",]
The command that works with the channels stored
client.on("message", async (message) => {
if(message.author.bot) return;
if(ch.includes(message.channel.id)) {
const prompt = message.content
message.channel.sendTyping(60)
const answer = await ask(prompt);
message.channel.send(answer);
}});
Edit : I used array.push() in an command but it didn't work it didn't do anything
client.on('messageCreate', message => {
if (message.content === '..auto-on') {
if(!message.member.permissions.has("ADMINISTRATOR")) {
message.channel.reply("**You don't have permissions**")};
ch.push(`${message.channel.id}`)
message.channel.reply(`**Added ${message.channel} to auto-reply**`);
}});
Edit 2 : it worked but it get reset after restarting
If I understood you correctly you want to insert a new channel Id to an existing array, you can do that using the Array.push() method.
Example:
const arrayofItems = ["Flour", "Milk", "Sugar"]
console.log(arrayofItems) // ["Flour", "Milk", "Sugar"]
arrayofItems.push("Coffee")
console.log(arrayofItems) // ["Flour", "Milk", "Sugar", "Coffee"]
Make sure to learn the basics of JS before starting out with libraries such as Discord.JS.

How to remove the command from a message sending ${message.content} Discord.js

I am working on a command where you type ?send it will then delete your message and send the message through the node.js bot but right now it sends the full message including the command How am I able to make it so that it will remove the prefix and command and just send what follows the command
My Code
module.exports = {
name: `send`,
description: "sends a message through the bot",
execute (client, message, args){
const channel = client.channels.cache.get('id');
const Crash = message.guild.roles.cache.find(r => r.name === "Crash");
if(message.member.roles.cache.has(Crash.id)) {
message.delete({timeout: 100})
message.channel.send(`${message.content}`);
} else {
message.channel.send(`You cant use this command`);
}
}
}
It looks like you're using a command handler that passes the args of the message command to the 3rd parameter of the execute function. I'm not sure how your command handler works but I assume args is an array of words in the message, not including the command or prefix. Try using the Array.join() function to join the args into a single string (with a space in between the words) and send that:
message.channel.send(`${args.join(" ")}`);

ChatBot back to previous dialog

I've created a chatbot using Node.js and the flow of dialogs works fine til the endDialog. Im having issues implementing a back option so it can jump back only to previous dialog. Can anyone please guide me how to solve that?
.reloadAction(
"restartBenefits", "Ok. Let's start over.",
{
matches: /^start over$|^restart$/i
}
)
.cancelAction(
"cancelRequest", "Thank you for reaching out, Good bye!",
{
matches: /^nevermind$|^cancel$|^cancel.*request/i,
confirmPrompt: "This will cancel your request. Are you sure?"
}
);
Use a customAction
In this case you can listen for whatever you want to be a keyword for "back" and then simply route the user back to that dialog using replaceDialog
bot.customAction({
matches: /back|last/gi, //whatever prompts you want to match.
onSelectAction: (session, args, next) => {
session.replaceDialog(PreviousDialog); //variable with the last dialog in it, set that somewhere, such as the end of your previous dialog
}
})
I think that at the final step of the dialog waterfall you need to add the last lines in this sample step:
/**
* This is the final step in the main waterfall dialog.
* It wraps up the sample "book a flight" interaction with a simple confirmation.
*/
async finalStep(stepContext) {
// If the child dialog ("bookingDialog") was cancelled or the user failed to confirm, the Result here will be null.
if (stepContext.result) {
const result = stepContext.result;
// Now we have all the booking details.
// This is where calls to the booking AOU service or database would go.
// If the call to the booking service was successful tell the user.
const timeProperty = new TimexProperty(result.travelDate);
const travelDateMsg = timeProperty.toNaturalLanguage(new Date(Date.now()));
await stepContext.context.sendActivity(ActivityFactory.fromObject(this.lgTemplates.evaluate('BookingConfirmation', {
Destination: result.destination,
Origin: result.origin,
DateMessage: travelDateMsg
})));
}
// =====> HERE: Restart the main dialog with a different message the second time around
return await stepContext.replaceDialog(this.initialDialogId, { restartMsg: 'What else can I do for you?' });
}
Just change the this.initialDialogId accordingly.

How to Separate Dialogs in Triggered Actions from Prompts (Microsoft Botbuilder SDK)

Note
I am using Microsoft Botbuilder SDK in node.js using ES6 babel.
The Problem
I have a dialog, '/MainMenu', that prompts the user for a free-response reply in order to dive into another, more relevant dialog. I also, however, want the user to be able to trigger an action that is completely irrelevant to subject matter (i.e. a dialog asking the bot, "how are you?"), returning back to the original MainMenu dialog just as they left off. I understand that in the documentation for the SDK, onSelectAction can be used to put the triggered dialog on top of the stack rather than replacing the entire stack, but once the '/HowAreYou' dialog ends, the bot also thinks that response was for the initial MainMenu prompt, replying with "I didn't understand. Please try again," like so:
Code
// I am using the builder.Library routing standard, and have confirmed that
// this gets triggered as expected. this dialog exists in a different file
lib.dialog('/HowAreYou', [
(session, args, next) => {
session.send('I\'m doing well. Thanks for asking!');
builder.Prompts.text(session, 'How are you doing today?');
}, (session, results) => {
session.endDialog('Good to hear that!');
}
]).triggerAction({
matches: /^how are you?$/i,
onSelectAction: (session, args, next) => {
// Add the help dialog to the top of the dialog stack (override the
// default behavior of replacing the stack)
session.beginDialog(args.action, args);
}
});
bot.dialog('mainMenu', [
(session, args, next) => {
builder.Prompts.text(session, 'Hi there! What can I do for you today?');
},
(session, results) => {
session.endConversation('Goodbye!');
}
]).beginDialogAction('weatherAction', '/Weather', {
matches: /^weather$/i,
}).beginDialogAction('sportsAction', '/Sports', {
matches: /^sports$/i,
}).beginDialogAction('cookingAction', '/Cooking', {
matches: /^cooking$/i,
});
Desired Behavior
Although the current result is very close to the desired behavior, I ideally want the bot to reply with the same MainMenu prompt it began with, without saying it didn't understand after the HowAreYou dialog finishes.
The Question
Is this possible? If so, how? If not, what are alternatives?
Thank you for any help you can give.
There is an explain on GitHub at https://github.com/Microsoft/BotBuilder/issues/2421, with what we can realize that the Prompts are built-in dialog which will handle the input validation.
As you first step in the Prompts dialog in mainMenu, and then you trigger the HowAreYou dialog when the first Prompts dialog is waiting for the input text.
Then the HowAreYou end as session.endDialog('Good to hear that!'); without an result for the first Prompts dialog in mainMenu, which is failed in the validation.
the root cause should be equivalent to input an empty text for builder.Prompts.text(session, 'Hi there! What can I do for you today?');.
update
Found the promptAfterAction property of IPromptOptions for prompt dialog, and i think about this issue. I think this should be by design.
As the sentence I don't understand... is a default text for property retryPrompt. So when you end the HowAreYou dialog and back the to mainPage dialog stack. The prompt dialog will restart and send the retryPrompt to user, which raises your issue.
For the accessibility, you can try to use:
builder.Prompts.text(session, 'Hi there! What can I do for you today?', {
retryPrompt: 'Hi there! What can I do for you today?'
});

Node.js synchronous prompt

I'm using the prompt library for Node.js and I have this code:
var fs = require('fs'),
prompt = require('prompt'),
toCreate = toCreate.toLowerCase(),
stats = fs.lstatSync('./' + toCreate);
if(stats.isDirectory()){
prompt.start();
var property = {
name: 'yesno',
message: 'Directory esistente vuoi continuare lo stesso? (y/n)',
validator: /y[es]*|n[o]?/,
warning: 'Must respond yes or no',
default: 'no'
};
prompt.get(property, function(err, result) {
if(result === 'no'){
console.log('Annullato!');
process.exit(0);
}
});
}
console.log("creating ", toCreate);
console.log('\nAll done, exiting'.green.inverse);
If the prompt is show it seems that it doesn't block code execution but the execution continues and the last two messages by the console are shown while I still have to answer the question.
Is there a way to make it blocking?
With flatiron's prompt library, unfortunately, there is no way to have the code blocking. However, I might suggest my own sync-prompt library. Like the name implies, it allows you to synchronously prompt users for input.
With it, you'd simply issue a function call, and get back the user's command line input:
var prompt = require('sync-prompt').prompt;
var name = prompt('What is your name? ');
// User enters "Mike".
console.log('Hello, ' + name + '!');
// -> Hello, Mike!
var hidden = true;
var password = prompt('Password: ', hidden);
// User enters a password, but nothing will be written to the screen.
So give it a try, if you'd like.
Bear in mind: DO NOT use this on web applications. It should only be used on command line applications.
Update: DO NOT USE THIS LIBRARY AT ALL. IT IS A TOTAL JOKE, TO BE PERFECTLY FRANK.
Since Node.js 8, you can do the following using async/await:
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
function readLineAsync(message) {
return new Promise((resolve, reject) => {
rl.question(message, (answer) => {
resolve(answer);
});
});
}
// Leverages Node.js' awesome async/await functionality
async function demoSynchronousPrompt() {
var promptInput = await readLineAsync("Give me some input >");
console.log("Won't be executed until promptInput is received", promptInput);
rl.close();
}
Since IO in Node doesn't block, you're not going to find an easy way to make something like this synchronous. Instead, you should move the code into the callback:
...
prompt.get(property, function (err, result) {
if(result === 'no'){
console.log('Annullato!');
process.exit(0);
}
console.log("creating ", toCreate);
console.log('\nAll done, exiting'.green.inverse);
});
or else extract it and call the extracted function:
...
prompt.get(property, function (err, result) {
if(result === 'no'){
console.log('Annullato!');
process.exit(0);
} else {
doCreate();
}
});
...
function doCreate() {
console.log("creating ", toCreate);
console.log('\nAll done, exiting'.green.inverse);
}
Old question, I know, but I just found the perfect tool for this. readline-sync gives you a synchronous way to collect user input in a node script.
It's dead simple to use and it doesn't require any dependencies (I couldn't use sync-prompt because of gyp issues).
From the github readme:
var readlineSync = require('readline-sync');
// Wait for user's response.
var userName = readlineSync.question('May I have your name? ');
console.log('Hi ' + userName + '!');
I'm not affiliated with the project in any way, but it just made my day, so I had to share.
I've come across this thread and all the solutions either:
Don't actually provide a syncronous prompt solution
Are outdated and don't work with new versions of node.
And for that reason I have created syncprompt
. Install it with npm i --save syncprompt and then just add:
var prompt = require('syncprompt');
For example, this will allow you to do:
var name = prompt("Please enter your name? ");
It also supports prompting for passwords:
var topSecretPassword = prompt("Please enter password: ", true);
Vorpal.js is a library I made that has just recently been released. It provides synchronous command execution with an interactive prompt, like you are asking. The below code will do what you are asking:
var vorpal = require('vorpal')();
vorpal.command('do sync')
.action(function (args) {
return 'i have done sync';
});
With the above, the prompt will come back after a second is up (only after callback is called).
This is dependency free, synchronous and works on Windows, Linux and OSX:
// Synchronously prompt for input
function prompt(message)
{
// Write message
process.stdout.write(message);
// Work out shell command to prompt for a string and echo it to stdout
let cmd;
let args;
if (os.platform() == "win32")
{
cmd = 'cmd';
args = [ '/V:ON', '/C', 'set /p response= && echo !response!' ];
}
else
{
cmd = 'bash';
args = [ '-c', 'read response; echo "$response"' ];
}
// Pipe stdout back to self so we can read the echoed value
let opts = {
stdio: [ 'inherit', 'pipe', 'inherit' ],
shell: false,
};
// Run it
return child_process.spawnSync(cmd, args, opts).stdout.toString().trim();
}
const buffer = Buffer.alloc(1024);
require("fs").readSync(process.stdin.fd, buffer);
console.log(buffer.toString());
You can use prompt-sync
const prompt = require('prompt-sync')()
const ans = prompt('How many more times? ') // get input from the user.
P.S. prompt-sync acts weird, if prompt message contains new line character, so if you need multiline prompt just use console.log():
const prompt = require('prompt-sync')()
console.log('How many more times?\n')
const ans = prompt('') // get input from the user.

Resources