Botbuilder - How to ignore user response without exiting prompt dialog - node.js

I have a multipatform bot (node.js through Azure Botframework) that uses a series of prompts to play a game with the user.
In group mode, such as on Kik or Slack, it waits for responses addressed to the bot.
However, I haven't found a way to simply ignore a message that doesn't address the bot. The solution I found ages ago was to simply reply with a new blank prompt:
builder.Prompts.text(session, "");
And this worked fine. However recently Slack must have changed something, because now this causes an error and the bot restarts.
How do I make the bot ignore certain responses without ending the dialog?
If suggesting a duplicate, please ensure it actually addresses this issue. Many other questions allow for the dialog to end, however this would interrupt the game.

You can setup middleware, as mentioned by Gary, that intercepts incoming messages and only processes it if the bot is #mentioned:
bot.use({
botbuilder: function (session, next) {
var message = session.message;
var botMri = message.address.bot.id.toLowerCase();
var botAtMentions = message.entities && message.entities.filter(
(entity) => (entity.type === "mention") && (entity.mentioned.id.toLowerCase() === botMri));
if (botAtMentions && botAtMentions.length) {
next();
}
},
send: function (event, next) {
next();
}
})

Related

How can I send a message from bot framework sdk after a period of inactivity? In nodejs

I am using the nodejs SDK for Bot Framework to develop a chatbot. I want to send a message to the user if they do not write in 5 minutes.
I do not find an example in bot-framework documentation and, in stackoverflow there are not solutions for a started bot (I do not need it to start the conversation). Where do I need to create the code? I have an index.js and a dialog file. How can I set the timer and restart it when the user send a message?
I'm using directline.
Thanks
There are two different ways you can approach this, one for directline only using events and one for all channels using setTimeout. The directline solution requires some code on your webchat client, but the latter requires you to save the conversation reference and start a new bot adapter. Both approaches could work.
Directline Only
You need to set up your webchat client to set up the timer and send an event to your bot if no activities are sent before the timer expires. You need to create a custom store to do this. Here is an example I used in the past:
const store = window.WebChat.createStore({}, function(dispatch) { return function(next) { return function(action) {
if (action.type === 'WEB_CHAT/SEND_MESSAGE') {
// Message sent by the user
clearTimeout(interval);
} else if (action.type === 'DIRECT_LINE/INCOMING_ACTIVITY' && action.payload.activity.name !== "inactive") {
// Message sent by the bot
clearInterval(interval);
interval = setTimeout(function() {
// Notify bot the user has been inactive
dispatch.dispatch({
type: 'WEB_CHAT/SEND_EVENT',
payload: {
name: 'inactive',
value: ''
}
});
}, 300000)
}
return next(action);
}}});
This will send an event to your bot with the name 'inactive'. Now you need to set up your bot to handle it. So in your this.onEvent handler you need to do something like this:
if (context.activity.name && context.activity.name === 'inactive') {
await context.sendActivity({
text: 'Are you still there? Is there anything else I can help you with?',
name: 'inactive'
});
}
All channels
As I'm typing this up, I'm realizing you should be able to emit the event from your bot itself and forego starting a new bot adapter instance. But I haven't tried that before, so I'm providing my existing solution. But you may wish to experiment with emitting an inactive event if the timeout is reached instead of the actions below.
That said, here is a solution you can use within your this.onMessage handler.
// Inactivity messages
// Reset the inactivity timer
clearTimeout(this.inactivityTimer);
this.inactivityTimer = setTimeout(async function(conversationReference) {
console.log('User is inactive');
try {
const adapter = new BotFrameworkAdapter({
appId: process.env.microsoftAppID,
appPassword: process.env.microsoftAppPassword
});
await adapter.continueConversation(conversationReference, async turnContext => {
await turnContext.sendActivity('Are you still there?');
});
} catch (error) {
//console.log('Bad Request. Please ensure your message contains the conversation reference and message text.');
console.log(error);
}
}, 300000, conversationData.conversationReference);
Note that you have to get and save the conversationReference if you go this route, so that you can call continueConversation if the timer expires. I typically do this in my this.onMessage handler as well just to make sure I always have a valid conversation reference. You can get it with the below code (I'm assuming you already have your conversation state and state accessor defined).
const conversationData = await this.dialogState.get(context, {});
conversationData.conversationReference = TurnContext.getConversationReference(context.activity);
Now as I mentioned in the first solution, I believe you should be able to send an inactivity event in your try block instead of initiating the bot adapter. If you try that and it works, please let me know so I can update this solution!

'Until loop' analogue needed - in order to continue bot dialog - after some status 'marker' is updated

'Until loop' analogue needed to continuously read status variable from helper function - and then (when the status variable is 'as we need it') - to resume bot conversation flow.
In my bot (botbuilder v.3.15) I did the following:
During one of my dialogues I needed to open external url in order
to collect some information from the user through that url.
After that I posted collected data (with conversation ID and other info) from that url to my bot app.js
file
After that I needed to resume my bot conversation
For that I created helper file - helper.js in which 'marker' variable is 'undefined' when the data from url is not yet collected, and 'marker' variable is some 'string' when the data is collected and we can continue our bot conversation
helper.js
var marker;
module.exports = {
checkAddressStatus: function() {
return marker;
},
saveAddressStatus: function(options) {
marker = options.conversation.id;
}
}
I can successfully update variable 'marker' with my data, by calling saveAddressStatus function from app.js.
However, when I get back to writing my code which is related to bot conversation flow (To the place in code after which I opened url - in file address.js, and from where I plan to continuously check the 'marker' variable whether it is already updated - in order to fire 'next()' command and continue with session.endDialogWithResult -> and then -> to further bot conversation flows - I cannot find the equivalent of 'until loop' in Node.js to resume my session in bot dialog - by returning 'next()' and continuing with the bot flow.
address.js
...
lib.dialog('/', [
function (session, args, next) {
...
next();
},
function (session, results, next) {
// Herocard with a link to external url
// My stupid infinite loop code, I tried various options, with promises etc., but it's all not working as I expect it
while (typeof helper.checkAddressStatus() == 'undefined') {
console.log('Undefined marker in address.js while loop')
}
var markerAddress = helper.checkAddressStatus();
console.log(markerAddress);
next(); // THE MOST IMPORTANT PART OF THE CODE - IF markerAddress is not 'undefined' - make another step in dialog flow to end dialog with result
function(session, results) {
...session.endDialogWithResult({markerAddress: markerAddress})
}
...
Any ideas how to make a very simple 'until loop' analoque in this context - work?
Having your bot stop and wait for a response is considered bad practice. If all of your bot instances are stuck waiting for the user to fill out the external form, your app won't be able to process incoming requests. I would at least recommend adding a timeout if you decide to pursue that route.
Instead of triggering your helper class in the endpoint you created, you should send a proactive message to the user to continue the conversation. To do this, you will need to get the conversation reference from the session and encode it in the URL that you send to the user. You can get the conversation reference from the session - session.message.address - and at the very least you will need to encode the bot id, conversation id, and the serviceUrl in the URL. Then when you send the data collected from the user back to the bot, include the conversation reference details for the proactive message. Finally, when your bot receives the data, recreate the conversation reference and send the proactive message to the user.
Here is how your conversation reference should be structured:
const conversationReference = {
bot: {id: req.body.botId },
conversation: {id: req.body.conversationId},
serviceUrl: req.body.serviceUrl
};
Here is an example of sending a proactive message:
function sendProactiveMessage(conversationReference ) {
var msg = new builder.Message().address(conversationReference );
msg.text('Hello, this is a notification');
msg.textLocale('en-US');
bot.send(msg);
}
For more information about sending proactive messages, checkout these samples and this documentation on proactive messages.
Hope this helps!

Chatbot messages do not show up in Facebook Messenger chat heads

I am developing a chatbot for Facebook Messenger using Microsoft Bot Framework. The bot sends the user proactive messages (reminders). Unfortunately, for some reason the messages never show up in a chat head (the Android widget for conversations), nor pop up a chat head if it wasn't present on the screen before. It does happen for other chatbots (Jarvis, for example).
This is the code that sends the reminders:
Reminder.find({ next_reminder: { $lte: new Date() } }, (err, res) => {
if (err !== null) {
return console.error(err);
}
res.forEach(reminder => {
// Build a notification message and address it to user who created the reminder
const msg = new builder.Message().text('...');
bot.beginDialog(reminder.user_address, '*:/sendReminder', {message: msg, nudnik: nudnik});
});
});
};
};
I have also tried bot.send(msg, () => ....) and session.beginDialog('sendReminder', msg). However, there is still no indication from Messenger when the message is received. What could go wrong here?
OK, I figured it out! Apparently, the default notification setting for a Facebook message is not to show a notification. To change it, in NodeJS you should add channel-specific data to the message with the following code:
msg = msg.sourceEvent({
facebook:
{notification_type: 'REGULAR'}
});
You can discover more in official documentation by Microsoft (here and here) and also in this Github discussion.

builder.Prompts.text not working after being hosted remotely

I have a simple bot that fetches news articles based on a user prompt. The entire flow works fine locally using emulator but after being deployed to a server the bot fails when it hits a builder.Prompts.text block. Below is my code and you will see a "Asking article count" prompt which is where it stops in flow.
Bot shows accepted when testing on the BOT Framework page
Bot is receiving messages via WebChat and Slack
Bot also shows 0 issues for each channel after interacting
var bot = new builder.UniversalBot(connector);
var intents = new builder.IntentDialog();
bot.dialog('/', intents);
var HHCC = require('./hhcc.js');
intents.matches(/^news/i, [
function(session) {
console.log("Intent Given!");
session.beginDialog('/news');
},
function(session, results) {
session.send('Enjoy reading!');
}
]);
bot.dialog('/news', [
function(session) {
console.log("Asking article count");
builder.Prompts.text(session, 'How many articles would you like to see?');
},
function(session, results) {
session.sendTyping();
session.conversationData.count = results.response;
HHCC.getNews(session.conversationData.count, session, function(newsArticles) {
newsArticles.forEach(function(newsCard) {
session.send(newsCard);
});
session.conversationData.news = newsArticles;
console.log(newsArticles);
session.endDialog();
});
}
]);
server.post('/api/messages', connector.listen());
Ive checked all logs and can't seem to find any clues as its failing pretty silently.
Have you attempted using builder.Prompts.number() instead of .text()? It only accepts numbers and (I'm guessing you're doing this) you won't have to parse the results.response into a number. Without provided error messages or logs it's difficult to help.
One thing you might have to look out for (if using builder.Prompts.number) is if a user provides a decimal, as the prompt will accept this input, requiring the bot to round to the nearest integer.
Also, if you've saved the results.response into your session object, you will not need to pass in session.conversationData.count as another parameter to HHCC.getNews(). You can instead access it from session in your function.

Botbuilder Dialogues dont support debounce or separate loops

How to achieve the following?
There are two types of listeners in botframework
one to the root dialog / where luis handler is attached to.
another is Prompts where an input from user is sought.
In both occassions. it is possible that user enters the inputs in a series of utterances and not necessarily only one.
how can Prompts in botbuilder collect in debounce pattern, esp when in middle of seeking inputs from user? and how can these be directed to luis while in middle of dialog?
e.g.
1.
bot: please enter your name
user: 1 sec
user: ok, it is Smith.
2.
bot: fetching the details from server... ( 5 sec pause makes user lose patience)
user: u there?
// This should not break the current dialog ( i.e. dialogue handler is preparing a response).
bot: yes, I am there.still waiting for a response from server. pls hold on. (after few secs...)
bot: got the details. here you go..
third example.
bot: what was your experience?
user: well, where shall I begin?
user: it was kind of ok, but...
user: not very good..
user: but would recommend
the prompts should be able to collect these before reacting to each input...
Here is what Im doing: using the library "debounce-queue" Im queueing the user messages in the bot.use({receive}) middleware. When the array of events get debounced, I proceed to merge the events (text, attachments, etc). After thats done I proceed to execute the next() middleware callback. Here is some code (also using lodash _), you might need to adapt to yours:
var debounces = {};
bot.use({
receive: (event, next) => {
if (event.type === 'message') {
if (!debounces[event.address.user.id]) {
debounces[event.address.user.id] = debounce(events => {
var last = events[events.length-1];
var event = last.event;
_.reverse(events.splice(0, events.length - 1)).forEach(debounced => {
event.text = debounced.event.text + ' ' + event.text;
event.attachments = _.concat(event.attachments, debounced.event.attachments);
})
last.next()
})
}, 2000)
}
debounces[event.address.user.id]({ event, next });
} else {
next()
}
}
})

Resources