Я тут подумал ещё раз о том боте, который вроде как должен будет вместо меня следить за ненадёжными тестами, и внезапно понял одну штуку. Все сценарии, с которыми я игрался, были так или иначе построены на инициируемых пользователем диалогах. Тот шлёт сообщение, бот отвечает, шлёт ещё одно, бот опять отвечает, и т. п. Но в моём случае, получив задание, бот будет работать молча, и, лишь заметив что-нибудь любопытное, должен начать беседу сам. Microsoft называет такие сценарии проактивными сообщениями, и вот как это можно провернуть на Microsoft Bot Framework.
Сохраняем контекст разговора
Основная идея очень простая. Во время любого диалога с пользователем бот может сохранить контекст разговора — message.address
— у session
объекта, и потом пользоваться им, когда приспичит общаться самому.
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 |
Причём, для запуска диалогов такой подход тоже работает:
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); } // .... |
Но тут надо иметь ввиду несколько важных моментов. Во-первых, отправляя такое сообщение, бот уже может быть в самом разгаре какой-нибудь другой беседы, и прервать её совершенно неожиданным образом может оказаться сбивающей с толку грубостью. Во-вторых, кроме очень уж странного способа отправлять сообщения через объект bot
вместо session
, в сессии же мы хранили и пользовательские данные — userData
, conversationData
и т.п. Где же их теперь брать?
Восстанавливаем сессию
nodejs
‘совская документация от Bot Framework не отличается подробностью, но так как речь идёт о JavaScript, самую актуальную и полную документацию можно получить непосредственно из сырцов. И вот таким нехитрым образом можно узнать, что у bot
объекта есть функция loadSession
, которая получает на вход тот самый address
, а на выходе отдаёт восстановленную сессию!
1 2 3 4 5 6 7 |
// ... function sendMessage (address, message) { bot.loadSession(address, function (e, session) { session.send(message); }); }; // ... |
А уж как только мы раздобыли сессию назад, то тут будут и привычный способ отправлять сообщения, и сессионные данные, и даже функция под названием curDialog
, которая вернёт имя текущего диалога, если такой проходит сейчас, и даст шанс отложить проактивное сообщение до лучших времён.
Мораль
Как видно из примеров, отправлять проактивные сообщение очень легко. Достаточно раздобыть адрес существующего разговора, а там уже можно отправлять сообщения и через бота, и через сессию, или даже начинать абсолютно новый диалог.
Но тут есть интересный момент. А что если разговор явно закончить? Была же какая-то функция вроде endConversation.
Останется ли валидным сохранённый адрес? Я бы ставил на то, что нет, но кто его знает. Если разговор идёт через SMS, то как бы канал всегда открыт, так что почему бы и нет. В общем, загадка. Но легко проверяемая.