I was thinking again about that bot, who supposedly will monitor unreliable tests for me, and suddenly realized one thing. All examples I dealt with were dialog based. You know, user sends the first message, bot responds, etc. But the bot I’m thinking about is different. Initial conversation indeed starts like a dialog. But once bot starts monitoring unit test statistics and finds something that I should take a look at, he needs to talk first! Microsoft calls such scenario sending proactive messages and there’re few tricks how to make that possible.
Saving conversation context
So the idea they suggesting is the following. Whenever conversation with user starts, we can save its session
‘s message.address
property and then use it to send proactive messages whenever we need to.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
const builder = require("botbuilder"); const connector = new builder.ConsoleConnector().listen(); let savedConversationAddress = null; const bot = new builder.UniversalBot(connector, function (session) { session.send("> I've remembered your conversation address and now will bother you proactively"); savedConversationAddress = session.message.address; setTimeout(() => sendMessage(savedConversationAddress, "> 5 seconds passed, yet I'm still talking"), 5000); } ); function sendMessage (address, message) { const msg = new builder.Message().address(address); msg.text(message); msg.textLocale("en-US"); bot.send(msg); }; console.log("> Proactive-bot has started"); |
1 2 3 4 5 6 |
$ node bot.js # > Proactive-bot has started # Hey # > I've remembered your conversation address and now will bother you proactively # # > 5 seconds passed, yet I'm still talking |
The same approach works for dialogs as well.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// .... const bot = new builder.UniversalBot(connector, function (session) { session.send("> I've remembered your conversation address and now will bother you proactively"); savedConversationAddress = session.message.address; setTimeout(() => startDialog(savedConversationAddress, "someDialogName"), 5000); } ); function startDialog (address, dialogName) { bot.beginDialog(address, dialogName); } // .... |
However, there’re few things to keep in mind. Firstly, when bot sends such proactive message, it already might be in the middle of ongoing conversation, so interrupting it with unrelated message might feel weird. Then, and this is quite obvious, as delayed message doesn’t have access to session
and its send
method, we have to use this weirdly looking bot
API.
Finally, how do I get access to the state? userData
, conversationData
– it’s properties of session
object and I don’t have any. Apparently, there’s slightly different way to send proactive messages, which takes all that into account.
Getting the session back
nodejs
part of Bot Framework documentation is not that complete, but the source code is, so I noticed that bot
object has a nice method called loadSession
, which takes saved conversation address as an input and returns the session back through a callback.
1 2 3 4 5 6 7 |
// ... function sendMessage (address, message) { bot.loadSession(address, function (e, session) { session.send(message); }); }; // ... |
As soon as we’ve got the session object back, not only we’re getting a familiar way of sending messages, but also a full access to user data and nice function called curDialog
, which will return ongoing dialog name, if any. That’s comes in handy, when we want to wait until ongoing conversation finishes, before starting to bombard user with new messages.
Conclusion
As you can see, sending proactive messages is quite easy. It’s all focused around getting access to conversation context and then there are multiple ways to send messages with it: via bot
object or by restoring the session
, sending individual messages or initiating the whole dialog. That’s convenient. I wonder, however, how this approach works when users explicitly ends conversation. I’d bet on saved conversation context value becoming invalid, but who knows. Maybe it depends on a channel.