Botbuilder Dialogues dont support debounce or separate loops - node.js

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()
}
}
})

Related

'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!

Telegram Bot API. How to collect images only between start and end messages?

I'm writing Telegram bot (nodejs) which will collect all images sent to it between "start" and "end" messages. I learned how to start bot.onText(/\/start/, but how to react on "end" message from user to start reacting after that?
You need to maintain state for every user who is going to send you the /start and /end command. You can persist the state in a Key/Value store (e.g. { userid: xxx, end: false }. You can then check against the database store every time a picture is sent. An example of how your code would look like is:
bot.onText(/\/start/, msg => {
//saveToDb({chat_id: msg.chat.id, completed: false});
});
bot.onText(/\/end/, msg => {
//saveToDb({chat_id: msg.chat.id, completed: true});
});
bot.on("message", msg => {
// most of this code is just for logical purposes to explain the concept
if (typeof msg.image === "object") {
//const completed = checkDb(msg.chat.id);
if (completed !== true) {
// work with the image
}
}
});
Alternatively you can look into mau its aim is to solve this issue. It works well with node-telegram-bot-api, check the examples folder to get started on how it works.

Botbuilder - How to ignore user response without exiting prompt dialog

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();
}
})

Bot Framework Node.js ad hoc message TO A SPECIFIC USER

I have been staring at this for hours and can't find a solution and that is even though by all suggestions it SHOULD be quite easy - https://learn.microsoft.com/en-us/bot-framework/nodejs/bot-builder-nodejs-proactive-messages.
I have created a simple code which will "register" the user and save their data in my cosmosDatabse on Azure. That works perfectly.
//ON "register" SAVE USER DATA AND SAY REGISTERED MESSAGE
bot.dialog('adhocDialog', function(session, args) {
var savedAddress = session.message.address;
session.userData.savedAddress = savedAddress;
//REGISTERED MESSAGE
session.endDialog("*Congratulations! You are now registered in our network! (goldmedal)*");
})
.triggerAction({
matches: /^register$/i
})
But how can I then access that specific user and send him a message if, say, a condition is met? (in fact on HTTP request)
I am fairly certain we have to write the conversation ID or user ID somewhere. The question is where?
function startProactiveDialog(address) {
bot.beginDialog(address, "A notification!");
}
This is how simple I think it should be. But where do you specify the user then?
You've saved the address of the user inside of your database by saving it to session.userData.savedAddress. When the event triggers, perform a query to your database that checks for the users that meet two criteria.
They're registered to listen for the event
Their address has been saved inside of the database.
In your case, you can save a property to the session.userData object, a property that lists which events they're listening for. If you just need to send a message to the user, then you can simply use bot.loadSession(savedAddress) to ping the user.
Edit:
So instead of looking specifically by user ID, you should send a query to your CosmosDB that looks for entries that have a "listen-to" Boolean-type flag corresponding to the event.
You're not worrying about the user ID at first, you're just retrieving all entries with a query that would (broadly speaking) look like this:
SELECT * FROM BotState WHERE data LIKE 'listenForEvent=1.
So to setup your session.userData so that the above theoretical query would work, you would need to modify that snippet of code in your question to something like the following:
bot.dialog('adhocDialog', function(session, args) {
var savedAddress = session.message.address;
session.userData.savedAddress = savedAddress;
session.userData.listenForEvent = 1 // Our property we're going to look for.
session.endDialog("*Congratulations! You are now registered in our network! (goldmedal)*");
})
.triggerAction({
matches: /^register$/i
})
Actually, the savedAddress should be an instance of IAddress, and also, the function loadSession(address: IAddress, callback: (err: Error, session: Session) => void): void; and address(adr: IAddress): Message; under Message class all require IAddress as the parameter.
So first of all, you should save the entire address json object in cosmosDB for later using.
As botbuilder for Node.js is built on Restify or Express, you can build an addition route for your user to trigger and send proactive messages. The work flow could be following:
Guide user to register & Save the user's address object with the account mapping in your DB
Create a Route in Restify or Expressjs for trigger the proactive message:
server.get('/api/CustomWebApi', (req, res, next) => {
//find the user's address in your DB as `savedAddress`
var msg = new builder.Message().address(savedAddress);
msg.text('Hello, this is a notification');
bot.send(msg);
res.send('triggered');
next();
}
);
or if you want to leverage loadSession
server.get('/api/CustomWebApi', function (req, res, next) {
bot.loadSession(savedAddress, (err, session) => {
if (!err) {
session.send('Hello, this is a notification')
session.endConversation();
}
})
res.send('triggered');
next();
});
I created a users.json file, to which I save all the users. It works the way I need it to. I guess database would be better, but I don't really have a clue where to begin with that. Database is a whole new chapter I have not encountered yet, so it doesn't make sense to work on it when the project needs are resolved.

Chaining with Telegram Bot API (like TriviaBot)

I am creating a TriviaBot style bot for telegram and am using Node.js to do so. At the moment I am having trouble capturing the users response to my quiz to determine whether the user got the question right or wrong. Below is some code:
bot.onText(/\/quiz/, function (msg) {
var chatId = msg.chat.id;
var text = quizdata.one.msgtxt;
var opts = {
reply_to_message_id: msg.message_id,
reply_markup: JSON.stringify({
keyboard: quizdata.one.keyboard,
one_time_keyboard: true
})
};
bot.sendMessage(chatId, text, opts);
//NEED TO CAPTURE THE USER RESPONSE AND REPLY TO THEIR MESSAGE ACCORDINGLY
});
NOTE : Telegram would cut of any asynchronous function, you should make separated module for listening any incoming interaction with button. You could use global Array for to store small data to be able getting returned for other module you need.
Putting all of your command in the index js not good idea tho.
if you want to listen the from keyboard callback_data. Just create a new line to listen any incoming clicked button.
bot.on("callback_query", (msg) => {
if (msg.data === "your_keyboard_callback_data") {
// do whatever you want
}
})
For more clearance node telegram api
Sorry if my answer is too late for this but hope mine can help other people 🙂

Resources