Статьи

Сделайте Skype-бот с помощью Electron и Microsoft Bot Framework

Человекоподобный робот (созданный с помощью Microsoft Bot Framework?), Подающий кофе офисным работникам

Чат-боты становятся все более популярными. Facebook работает над созданием платформы для создания ботов Messenger, которая позволила бы владельцам бизнеса создавать поддержку своих клиентов полностью в приложении для обмена сообщениями Facebook. Заказываете пиццу , планируете следующий визит к врачу или просто пытаетесь найти самый дешевый рейс для своего следующего путешествия? Найдите контакт с ботом в своем приложении обмена сообщениями и спросите, что вам нужно, так же, как вы спросите контакт с человеком в вашем списке друзей.

Дэвид Маркус, вице-президент по продуктам обмена сообщениями в Facebook, выступил на веб-саммите в ноябре с докладом об участии Facebook в чат-ботах, и было совершенно ясно, что у Facebook большие планы, позволяющие клиентам и владельцам бизнеса интегрировать не только поддержку клиентов в чат Messenger, но и также некоторые взаимодействия, которые, как вы ожидаете, обычно представляют собой форму на веб-странице или в приложении (например, заказ еды, настройка следующей покупки автомобиля и т. д. ).

В этом руководстве мы будем использовать Electron и Microsoft Bot Framework (MBF) для создания бота Skype для ежедневных скрам-встреч.

Что там?

Если смотреть с технической стороны, то самой популярной платформой в настоящее время является Microsoft Bot Framework, которая позволяет вам подключить своего бота практически ко всем популярным чатам.

соединители каркаса ботов: Kik, Skype, Twilio, Telegram, Microsoft Teams, почта Office 365, Slack, Facebook Messenger, GroupMe

Но есть и другие варианты:

О нашем боте

Мы создадим графический интерфейс настройки для создания команд Scrum и добавления участников с Electron, а затем с помощью MBF создадим бота, который будет читать из конфигурации и предлагать всем добавленным пользователям три вопроса ежедневных собраний Scrum. Когда все предоставят ответы, бот отправит резюме встречи всем участникам в команде.

Регби бот?

Мяч для регби

Нет, мы не делаем регби-бота. Для тех, кто не знаком с scrum, вот TL; DR :

Scrum — это методология, которая состоит из предопределенных правил и лучших практик для процесса гибкой разработки (например, команды разработчиков программного обеспечения), особенно для команд из ~ 3-6 человек (очень грубая и сильно различающаяся). Эти правила / лучшие практики могут состоять из таких вещей (опять же, очень грубо и сильно различающихся, потому что каждая команда приспосабливает их к своим потребностям):

  • Как создаются задачи и что они должны указывать
  • Метрики для расчета того, насколько быстро команда может выполнить итерацию продукта на основе предыдущего времени, необходимого для выполнения задачи.
  • Определенные роли каждого члена команды
    • Владелец продукта : человек, который вызывает выстрелы; беседует с заказчиком о разрабатываемом продукте и на основе требований заказчика создает пользовательские истории (причудливое название для задач), которые затем могут свободно выбирать разработчики
    • Разработчики : технические люди
    • Скрам мастер : садится за шею и следит за тем, чтобы вся команда действовала в соответствии с правилами схватки
  • Благоприятствует общению команды , особенно лицом к лицу
  • Встречи, которые должна иметь команда
    • Как часто проводятся эти встречи?
    • Что следует обсудить на тех встречах

Одной из таких встреч является ежедневная встреча. Обычно это делается первым делом утром, каждый член команды сообщает остальным членам команды о том, что они работали в предыдущий день, и какой прогресс был достигнут. Кроме того, каждый член команды раскрывает то, что он планирует делать сегодня, и, что не менее важно, любые препятствия, особенно те, которые будут препятствовать дальнейшей разработке задач.

Ежедневные скрам-встречи обычно проводятся «вживую», но для удаленных команд с разными часовыми поясами и / или разными местами это может стать проблемой. Вот тут и появляется бот, который мы делаем.

Конфигуратор GUI

Предпосылки:

Весь код как для бота, так и для конфигуратора можно найти в прилагаемом репозитории статьи .

Boilerplate

Если вы не знакомы с Electron, было бы неплохо взглянуть на эту статью (по крайней мере, на вступительные абзацы), в которой описываются основы Electron и причина, по которой он быстро завоевал популярность. Многие из новых настольных приложений, которые выходят, используют Electron (например, Slack, Visual Studio Code).

Чтобы настроить шаблонный код, мы будем использовать генератор Yeoman .

Перейдите в папку, в которой вы хотите разместить свой проект, и запустите следующую

npm install -g yo generator-electron 

Это установит пакет для Electron глобально на вашем компьютере. С этого момента вы можете вызывать генератор для электрона где угодно, что является нашим следующим шагом:

 yo electron 

Это обеспечит вас всеми файлами, необходимыми для запуска приложения Electron «Hello World». Он запустит npm install автоматически, поэтому, как только Yeoman завершит работу, вы можете запустить:

 npm start 

И вы должны увидеть всплывающее окно нового приложения.

Экран по умолчанию для новых приложений

Входная точка

index.js является точкой входа для приложения. Я рекомендую вам открыть этот файл и посмотреть, что происходит.

 function createMainWindow() { const win = new electron.BrowserWindow({ width: 600, height: 400 }); win.loadURL(`file://${__dirname}/index.html`); win.on('closed', onClosed); return win; } 

createMainWindow() создаст главное окно (говоря капитаном очевидно), вызвав конструктор класса BrowserWindow , и здесь вы можете указать некоторые параметры окна, такие как ширина, высота, цвет фона и многое другое .

В этой функции win.loadURL метод win.loadURL . Почему это важно? Здесь мы видим, что содержимое приложения на самом деле является ничем иным, как файлом HTML! Никакой магии и никаких новых функций или каркасов, чтобы научиться делать настольное приложение. Все, что для этого нужно, — это опыт веб-разработчика, что делает нас веб-разработчиками и разработчиками настольных приложений!

 const app = electron.app; app.on("window-all-closed", () => { // ... }); app.on('activate', () => { // ... }); app.on('ready', () => { // ... }); 

Electron предоставляет нам обратные вызовы к событиям, полный список здесь .

  • ready — если вы знакомы с jQuery, событие ready будет примерно таким же, как jQuery(document).ready() .

  • активировать — активация испускается каждый раз, когда окно приложения находится в фокусе.

  • windows-all-closed — запускается, когда все окна приложения закрыты, что делает его местом для любой очистки. Будьте осторожны с этим, так как в некоторых случаях он не будет вызван (например, если вы вызываете app.quit() из кода или если пользователь нажал Cmd + Q).

Логика приложения

Файл точки входа, index.js , содержит определенный код запуска и выхода приложения и больше используется для глобальной настройки. Мы не размещаем здесь логику приложения. Как мы уже видели, само приложение является не чем иным, как HTML-файлом. Итак, давайте перейдем к index.html и добавим некоторые элементы для нашего графического интерфейса конфигуратора.

 <!doctype html> <html> <head> <meta charset="utf-8"> <title>Electron boilerplate</title> <link rel="stylesheet" href="index.css"> </head> <body> <div class="container"> <section class="main"> <h2>Teams</h2> <div> <select id="teams"> <option>Select a team...</option> </select> <input type="text" id="newTeamName" placeholder="New team name..."/> <button id="addTeam" disabled type="button">+</button> </div> <div id="members"></div> <button id="addMember" type="button">+</button> <p id="message"></p> <button id="save" type="button">Save</button> </section> <footer></footer> </div> <script src="app.js"></script> </body> </html> 

Замените текущий HTML-файл этим кодом. В конце тела мы добавили ссылку на сценарий app.js , в который входит логика нашего приложения. Помните, что окно Electron — это не что иное, как окно браузера, встроенное внутрь, поэтому во время разработки вы можете использовать стандартные сочетания клавиш для повторного запуска кода (F5, Ctrl + R) и открытия Chrome-подобных инструментов разработчика (F12).

Добавьте новый файл в корень проекта, назовите его app.js и вставьте код отсюда . Здесь нет ничего нового, просто старый добрый JavaScript.

Для сохранения данных мы будем использовать простой файл JSON, которого более чем достаточно для наших нужд. В случае, если вы хотите увеличить масштаб приложения, его придется заменить решением для базы данных.

Есть кнопка для добавления новых команд, а затем внутри каждой команды мы можем добавлять участников. Каждый участник представлен своим именем пользователя в Skype. Позже, когда мы начнем создавать самого бота, вы увидите, что в эмулятор бота встроен чат-клиент, используемый для тестирования. Имя пользователя этого пользователя — пользователь .

Кроме того, мы можем загружать команды, выбирая название команды из выпадающего списка. В нижней части каждой команды есть поле ввода, которое представляет время дня, в течение которого мы должны проводить ежедневное собрание. Мы сохраним это значение как метку времени, которая представляет количество секунд с полуночи до времени встречи.

Давайте сейчас запустим конфигуратор и попробуем добавить команду и пользователя с именем «пользователь».

Экран управления командой с вариантами выбора или добавления команды

Ввод нового названия команды

«Новая команда добавлена!»

Теперь мы можем выбрать добавленную команду из выпадающего списка и добавить в нее нескольких пользователей.

Важно: Вы должны добавить пользователя с именем пользователя user, потому что эмулятор не может изменить имя, и это жестко запрограммированное имя. Чтобы бот узнал нас во время тестирования, он должен быть пользователем .

Установите время 00:00 (или что-нибудь еще) и нажмите Сохранить .

Проверьте файл teams.json , это должно быть его содержимое:

 { "alpha": { "members": { "user": {}, "almir bijedic": {} }, "time": 0 } } 

Это будет позже использовано ботом.

Бот

бот

Microsoft Bot Framework

MBF SDK выполнен в двух версиях: C # и Node.js. Мы будем использовать версию Node. Бот работает через REST API, который вы можете вызвать вручную или использовать предоставленный SDK с открытым исходным кодом. В этом уроке мы будем использовать SDK, так как он намного быстрее. Вызов API с пользовательскими функциями может быть лучшим вариантом в случае, если вам нужно интегрировать бота в существующее приложение или если по какой-то причине вы не можете использовать Node.js / C #.

Для локального тестирования бота есть два варианта:

  1. Используйте ConsoleConnector , который позволяет вам общаться с ботом через командную строку, или
  2. Используйте класс ChatConnector с restify (или чем-то еще) для запуска локального сервера и запуска эмулятора ботов, предоставляемого Microsoft, который действует как фиктивный пользователь на вашем локальном компьютере.

Мы перейдем к варианту номер два, так как он, скажем, «более реальный».

Маршруты

Основной класс для создания чат-бота называется UniversalBot . Стоит знать, что также существует UniversalCallBot , который позволит вам совершать звонки, но мы не будем освещать это в этом руководстве. Кроме того, смысл чат-бота заключается в том, чтобы позволить пользователям взаимодействовать без необходимости звонить, так как кажется, что мы предпочитаем отправлять сообщения вместо звонков .

Чтобы определить, как бот собирается отвечать на входящее сообщение от пользователя, мы используем маршруты. Это очень похоже на обычное веб-приложение, например:

 // bot is an instance of UniversalBot bot.dialog("/", function (session) { session.send("Hello World"); }); 

Обратите внимание, что bot здесь является экземпляром класса UniversalBot .

Это будет отправлять обратно «Hello World» пользователю каждый раз, когда пользователь отправляет сообщение боту.

bot.dialog() принимает два параметра: маршрут и функцию, которая выполняется, когда этот маршрут активен. В случае диалога модели водопада (водопад будет объяснен в следующем разделе), второй параметр может вместо этого быть массивом функций, которые затем будут выполняться одна за другой, таким образом взаимодействуя с пользователем.

Начальная настройка

Сейчас самое время попробовать это. Вернитесь к своему проекту Electron и добавьте новую папку внутри с именем bot . Запустите npm init внутри этой папки и заполните основную информацию, единственное, что вам нужно ввести, это app.js в качестве точки входа и node app.js в качестве стартового скрипта. После того, как вы закончите, создайте новый файл app.js в корне папки bot .

Теперь нам нужно установить зависимости для нашего бота.

 npm install --save botbuilder restify fs-extra 

Затем перейдите к файлу app.js который мы создали в папке bot и app.js нам библиотеки.

 // app.js var restify = require("restify"), builder = require("botbuilder"), fse = require("fs-extra"); 

Нам нужно создать сервер восстановления, который будет прослушивать входящие соединения через определенный порт.

 // app.js // Setup Restify Server var server = restify.createServer(); server.listen(process.env.port || process.env.PORT || 3978, function () { console.log("%s listening to %s", server.name, server.url); }); 

Теперь мы подключим сервер restify к сервису REST бота MBF.

 // Create chat bot var connector = new builder.ChatConnector({ appId: process.env.MICROSOFT_APP_ID, appPassword: process.env.MICROSOFT_APP_PASSWORD }); var bot = new builder.UniversalBot(connector); server.post("/api/messages", connector.listen()); 

Вы можете использовать переменную окружения MICROSOFT_APP_ID и MICROSOFT_APP_PASSWORD для Node, чтобы предоставить свои учетные данные для входа. Это используется для аутентификации в Microsoft Bot Directory.

Примечание. Альтернативой ChatConnector является ConsoleConnector , который запрашивает ввод данных в консоли запущенного приложения. Этот метод не требует эмулятора, который мы будем устанавливать через несколько минут

И последнее, но не менее важное: добавьте простой диалог в корневой маршрут, который будет выводить только «Hello World! пользователю.

 bot.dialog("/", function(session) { session.send("Hello World!"); }); 

Давайте посмотрим, как все это работает с точки зрения общей картины, поскольку это может немного сбивать с толку то, что мы делаем с ChatConnector здесь и зачем нам нужен сервер restify.

архитектура фреймворка

Пользователь добавил вашего бота в качестве контакта Skype.

  1. Пользователь отправляет сообщение от своего Skype-клиента боту. Это сообщение отправляется на серверы Skype, а затем направляется вашему боту, которого вы ранее зарегистрировали .
  2. Во время регистрации вы дали боту конечную точку https, которая указывает на сервер, на котором выполняется код вашего бота. Таким образом, серверы Skype пересылают сообщение на ваш сервер восстановления со всеми подробностями сообщения.
  3. ChatConnector получает этот запрос от сервера restify и обрабатывает его соответствующим образом (как вы и предполагали).
  4. Затем Bot Framework SDK генерирует ответ в соответствии с вашими потребностями и отправляет его обратно на сервер. Во время регистрации вы указали идентификатор и пароль приложения, которые необходимы для доступа вашего бота к серверам Skype. Бот получил местоположение REST API вместе с сообщением на шаге # 2.
  5. Серверы Skype распознают ответ и пересылают сообщение пользователю.

Чтобы протестировать этого простого бота, которого мы только что создали, нам нужно скачать и установить эмулятор, который действует как клиентское приложение Skype (пользователь) и сервер Skype REST API, который находится в левой части диаграммы выше.

Перейдите на страницу эмулятора и загрузите, установите и запустите его.

Эмулятор Microsoft Bot Framework

Теперь нам нужно предоставить эмулятору конечную точку, в которой выполняется наш код бота.

Вернитесь в папку с ботом и запустите npm start . Вы должны увидеть что-то вроде этого:

 restify listening to http://[::]:3978 

Вы можете изменить этот порт, PORT переменную окружения PORT Node или изменив жестко заданное значение отката 3978 в начале файла.

Это конечная точка на локальном хосте, на порту 3978. Итак, давайте поместим это в эмулятор. Кроме того, не забудьте, что мы слушаем маршрут / api / messages .

эмулятор коннект конфиг

Оставьте Microsoft App ID и пароль пустыми; поскольку мы проводим тест локально, в этом нет необходимости. Нажмите СОЕДИНИТЬ .

Теперь вы можете попробовать бот. Вы всегда получите сообщение Hello World, так как это все, что мы настроили до сих пор.

эмулятор привет мир

Нам понадобится бот умнее этого. В следующих разделах мы будем реализовывать следующие маршруты:

  • / — Маршрут корневого диалога будет использоваться только тогда, когда уже зарегистрированный пользователь отправляет сообщение боту в промежутке между встречами. Мы добавляем его с единственной целью показать пользователю, что что-то происходит, и бот все еще слушает, даже когда мы не участвуем в собрании или регистрации.
  • /firstRun — нам нужно как-то зарегистрировать пользователя и сохранить его адрес, чтобы иметь возможность отправить ему сообщение позже.
  • /dailyScrumDialog — с setInterval() будет работать таймер, который будет проверять время ежедневного совещания всех команд. Если есть команда, время встречи которой пришло, найдите всех пользователей, которые зарегистрированы в боте (под регистрацией мы подразумеваем пользователей, которые уже добавлены в команду в конфигураторе, И они также добавили бота в качестве контакта в Skype И они отправили боту хотя бы одно сообщение).
  • /report — Самый простой диалог, используемый только для отправки отчета о собрании всем членам команды. Это будет вызвано другой функцией, выполняющейся с setInterval() , проверяющей, завершил ли каждый член команды ответы на три вопроса. Если да, отправьте ответы всех участников каждому члену команды.

Водопад

Водопад — самый основной тип диалога с ботом. Он делает именно так, как кажется: он стекает вниз, не возвращаясь. Мы передаем массив функций в качестве второго параметра в функцию dialog бота. Каждая функция будет выполняться одна за другой при условии, что на предыдущем шаге будет приглашение.

builder.Prompts.text(session, "Message to send") — это основной способ запроса ввода от пользователя. После ответа пользователя выполняется следующая функция из массива. На этот раз он имеет два параметра: объект сеанса и объект результатов, который содержит сообщение пользователя.

 bot.dialog("/", [ function (session) { builder.Prompts.text(session, "Hey there, how are you doing?"); }, function (session, results) { console.log(results.response); // This will print out whatever the user sent as a message session.send("Great! Thank you for letting me know.") } ]); 

Замените предыдущий корневой диалог новым и попробуйте его.

Обратите внимание, что мы также можем сохранять и сохранять пользовательские данные.

 bot.dialog("/", [ function (session) { if (session.userData.howIsHe) { session.send(session.userData.howIsHe); } else { builder.Prompts.text(session, "Hey there, how are you doing?"); } }, function (session, results) { session.userData.howIsHe = results.response; session.send("Great! Thank you for letting me know.") } ]); 

Выполнение этого сохранит ответ пользователя и затем отправит ему ответ на каждое следующее сообщение.

эмулятор водопада

Стек диалогов

Как уже намекнуло, бот организует чат через диалоги. Когда начинается разговор с пользователем, бот выдвигает диалоговое окно по умолчанию поверх стека. Затем мы можем использовать одну из следующих функций, чтобы перенаправить их в другие диалоги и / или завершить их.

session.beginDialog (route, args, next)

Эта функция останавливает текущее диалоговое окно, добавляет диалоговое окно с указанным маршрутом поверх стека, и после завершения вновь вызванного диалогового окна оно возвращается к точке в предыдущем диалоговом окне, где beginDialog() .

session.endDialog ()

Когда мы вызываем endDialog() , текущий диалог извлекается из стека, и мы возвращаемся к следующему диалогу в стеке.

session.endDialogWithResult (арг)

То же, что endDialog() с той разницей, что мы можем передать некоторую переменную, которая будет использоваться вызывающим диалогом (следующий диалог в стеке).

session.replaceDialog (route, args, next)

В случае, если мы не хотим возвращаться к предыдущему диалоговому окну после создания нового, мы можем использовать replaceDialog() вместо beginDialog() .

session.cancelDialog (dialogId, replaceWithId, replaceWithArgs)

Отмена диалога приводит к тому, что диалоги выталкиваются из стека (отменяются) до тех пор, пока не будет достигнут диалог с предоставленным идентификатором, который затем также отменяется и управление возвращается исходному вызывающему. Затем этот вызывающий может также проверить переменную results.resumed чтобы обнаружить отмену.

Кроме того, вместо возврата к исходному абоненту, его также можно заменить, указав идентификатор диалога.

session.endConversation ()

Это удобный способ отменить все диалоги. По сути, это похоже на вызов session.cancelDialog(0) (0 — это идентификатор первого диалога в стеке, поэтому все диалоги будут отменены). Это удобно, когда вы также хотите очистить данные сеанса для пользователя.

Первый запуск промежуточного программного обеспечения

Бот не может общаться с пользователями Skype (или любой другой чат-платформой в этом отношении — не забывайте, что MBF работает с несколькими клиентами чата), пока пользователь не инициировал диалог с ботом. Имеет смысл не так ли? Главным образом, чтобы избежать спама.

Нам нужен адрес пользователя (среди прочего, объект, содержащий идентификатор пользователя и идентификатор диалога), чтобы иметь возможность инициировать диалог, поэтому нам нужна какая-то логика первого запуска, которая будет хранить адрес пользователя для последующего использования.

MBF предоставляет нам промежуточное программное обеспечение, которое мы можем использовать, чтобы указать маршрут, по которому мы хотим направить пользователя в первый раз, когда диалог начался.

 var version = 1.0; bot.use(builder.Middleware.firstRun({ version: version, dialogId: "*:/firstRun" })); 

Это направит пользователя, впервые регистрирующегося на маршрут «firstRun», который мы затем должны определить.

 bot.dialog("/firstRun", [ function (session, args) { if (session.userData.user && session.userData.team) { session.userData["BotBuilder.Data.FirstRunVersion"] = version; session.replaceDialog("/dailyScrum"); } else { builder.Prompts.text(session, "Hello... What's your team name?"); } }, function (session, results) { // We'll save the users name and send them an initial greeting. All // future messages from the user will be routed to the root dialog. var teams = readTeamsFromFile(); var providedTeamName = results.response.toLowerCase(); var user = session.message.user.name.toLowerCase(); if (teams[providedTeamName] && Object.keys(teams[providedTeamName].members).indexOf(user) > -1) { teams[providedTeamName].members[user].address = session.message.address; writeTeamsToFile(teams); session.userData.user = user; session.userData.team = providedTeamName; session.send("Hi %s, you are now registered for the %s team daily scrum. We will contact you at the time of the meeting, which is at %s", user, providedTeamName, timeToString(teams[providedTeamName].time)); } else { session.send("Wrong team! Try again 😀 (%s)", user); session.replaceDialog("/firstRun"); } } ]); function readTeamsFromFile() { return fse.readJsonSync("./data/teams.json"); } function writeTeamsToFile(teams) { fse.outputJsonSync("./data/teams.json", teams); } function timeToString(time) { return pad(parseInt(time / 60 / 60 % 24)) + ":" + pad(parseInt(time / 60) % 60) } function pad(num) { var s = "0" + num; return s.substr(s.length - 2); } 

Мы предоставляем две функции во втором массиве параметров, которые будут вызываться последовательно. После того, как пользователь предоставит ответ на первый, вызывается второй. В этом случае мы builder.Prompts.text(session, message)) у пользователя имя с помощью builder.Prompts.text(session, message)) а затем в следующем мы обрабатываем предоставленное имя команды путем поиска в нашем JSON с именами команды. Если имя команды найдено, мы добавляем имя пользователя в JSON и отправляем сообщение, информирующее пользователя о том, что он сейчас зарегистрирован, и будет запрошено во время разборки.

В дополнение к диалогу / firstRun у нас также есть некоторые вспомогательные функции.

readTeamsFromFile() вернет объект JSON из файла команд JSON.

writeTeamsTofile() принимает объект в качестве аргумента (в нашем случае это команды JSON) и записывает его обратно на диск.

timeToString принимает метку времени UNIX в качестве параметра и возвращает проанализированное время в виде строки.

pad используется для добавления дополнительных нулей в строку (например, 1 час 3 минуты должен быть 01:30, а не 1:30).

Добавьте предыдущие два фрагмента кода в наш bot/app.js вместе со следующим кодом, чтобы включить библиотеку fs-extra из npm, и давайте попробуем.

 var restify = require("restify"), builder = require("botbuilder"), fse = require("fs-extra"); 

Перед отправкой сообщения через эмулятор обязательно закройте эмулятор и запустите его снова (в функции удаления пользовательских данных эмулятора есть ошибка).

эмулятор первый запуск

Теперь вы можете зайти в файл data/teams.json и увидите, что у нас есть адрес пользователя эмулятора, сохраненный как объект.

 { "alpha": { "members": { "user": { "address": { "id": "3hk7agejfgehaaf26", "channelId": "emulator", "user": { "id": "default-user", "name": "User" }, "conversation": { "id": "5kaf6861ll4a7je6" }, "bot": { "id": "default-bot" }, "serviceUrl": "http://localhost:54554", "useAuth": false } } }, "time": 0 } } 

Мы также должны сделать что-то более значимое с корневым диалогом. Как только пользователь завершил /firstRun , мы должны вывести какое-то сообщение, чтобы пользователь знал, что что-то происходит.

 bot.dialog("/", function(session) { // this is a hack in order to avoid this issue // https://github.com/Microsoft/BotBuilder/issues/1837 if (!session.userData.team || !session.userData.user) { session.replaceDialog("/firstRun"); } else { session.send("Hello there, it's not yet scrum time. I'll get back to you later."); } }); 

Промежуточное

Промежуточное программное обеспечение первого запуска — это обычное промежуточное программное обеспечение, как и любое другое, реализованное по умолчанию в рамках. Мы также можем создать пользовательскую функцию промежуточного программного обеспечения. Идентификатор разговора можно изменить во время чата с пользователем Skype, поэтому мы хотим обновлять адрес (который содержит идентификатор разговора) в каждом сообщении, полученном от пользователя. Адрес будет передаваться с каждым сообщением, поэтому давайте добавим это в наш app.js

 bot.use({ botbuilder: function (session, next) { if (session.userData.team && session.userData.user) { var teams = readTeamsFromFile(); teams[session.userData.team].members[session.userData.user].address = session.message.address; writeTeamsToFile(teams); } next(); } }); 

Мы добавляем промежуточное программное обеспечение, используя функцию use класса UniversalBot . Он должен содержать объект с ключом botbuilder , значение которого является функцией, принимающей два параметра: сеанс и next функция.

Мы проверяем, зарегистрирован ли уже пользователь, проверяя, установлены ли переменные команды и пользователя в объекте userData сеанса. Если да, обновите адрес в файле JSON новым.

таймер

Следующим шагом будет добавление функции, которая будет проверять каждые x секунд, есть ли команда, чье ежедневное время встреч с участниками собрано. В случае, если встреча назначена, запустите маршрут «/ dailyScrum» с каждым членом команды, запустив с ним диалог на случай, если у нас есть адрес (пользователь зарегистрировался через «/ firstRun»). Если адреса нет, к сожалению, мы должны пропустить этого пользователя и запрашивать его только по завершении первого запуска.

 setInterval(function() { var teams = readTeamsFromFile(); Object.keys(teams).forEach(function(team) { if (shouldStartScrum(team)) { teamsTmp[team] = { members: {} }; Object.keys(teams[team].members).forEach(function(member) { if (teams[team].members[member].address) { bot.beginDialog(teams[team].members[member].address, "/dailyScrum", {team, member}); } }); } }); }, 3 * 1000); function shouldStartScrum(team) { var teams = readTeamsFromFile(); if (teams[team].time < 24 * 60 * 60 && getTimeInSeconds() > teams[team].time) { var nextTime = Math.round(new Date().getTime()/1000) - getTimeInSeconds() + 24 * 60 * 60 + teams[team].time; teams[team].time = nextTime; writeTeamsToFile(teams); return true; } else if (Math.round(new Date().getTime()/1000) > teams[team].time) { var nextTime = 24 * 60 * 60 + teams[team].time; teams[team].time = nextTime; writeTeamsToFile(teams); return true; } return false; } function getTimeInSeconds() { var d = new Date(); return d.getHours() * 60 * 60 + d.getMinutes() * 60; } 

Мы также должны добавить глобальную переменную teamsTmp вверху файла, чтобы сохранить ответы для каждого члена команды в памяти для генерации отчетов.

 var teamsTmp = {}; 

Обратите внимание на функцию shouldStartScrum , которая проверяет, находится ли отметка времени в файле JSON, который служит нашим хранилищем и связующим звеном между конфигуратором Electron и ботом. Я бы не рекомендовал использовать его в производственной среде. Это только для целей данного руководства, чтобы создать простой планировщик, чтобы показать возможности Bot Framework.

Ежедневный диалог схватки

Со всем, что мы узнали до сих пор, довольно просто добавить еще одно диалоговое окно с тремя вопросами в строке и сохранить данные для каждого ответа во временную переменную, чтобы в дальнейшем мы могли сгенерировать отчет. Это диалог, который будет инициирован таймером, созданным ранее.

 /* Add a dailyScrum dialog, which is called when it's a time for a daily scrum meeting, prompting the user in a waterfall fashion dialog */ bot.dialog("/dailyScrum", [ // 1st question of the daily function (session) { builder.Prompts.text(session, "What did you do yesterday?"); }, /* After the users answer the 1st question, the waterfall dialog progresses to the next function, with the 2nd question, but checking that the input for the previous question was not an empty string. If yes return the user to the first question by calling replaceDialog */ function(session, results) { if (results.response.length > 0) { teamsTmp[session.userData.team].members[session.userData.user] = { q1: results.response }; builder.Prompts.text(session, "What will you do today?"); } else { session.send("It can't be that you did nothing %s! Let's try this again.", session.userData.user); session.replaceDialog("/dailyScrum"); } }, // 3rd question function(session, results) { teamsTmp[session.userData.team].members[session.userData.user].q2 = results.response ; builder.Prompts.text(session, "Are there any impediments in your way?"); }, /* Finalize and schedule a report for the user. After the user has answered the third and last daily scrum question, set the isDone variable for that user to true */ function(session, results) { teamsTmp[session.userData.team].members[session.userData.user].q3 = results.response; teamsTmp[session.userData.team].members[session.userData.user].isDone = true; session.send("Got it! Thank you. When all the members finished answering you will receive a summary."); /* If the user is the first to finish for the team, create a checker function for the whole team, which will periodically check whether everyone from the team finished, if yes, send all the users in the team a report */ if (!teamsTmp[session.userData.team].checker) { teamsTmp[session.userData.team].checker = setInterval(function() { if (isEverybodyDone(session.userData.team)) { teamsTmp[session.userData.team].isDone = true; clearInterval(teamsTmp[session.userData.team].checker); var teams = fse.readJsonSync("./data/teams.json"); Object.keys(teamsTmp[session.userData.team].members).forEach(function(member) { bot.beginDialog(teams[session.userData.team].members[member].address, "/report", { report: createReport(session.userData.team) }); }); session.endDialog(); } }, 1000); } session.endDialog(); } ]); function isEverybodyDone(team) { var everybodyDone = true; Object.keys(teamsTmp[team].members).forEach(function (x) { if (!teamsTmp[team].members[x].isDone) { everybodyDone = false; } }); return everybodyDone; } function createReport(team) { // change to members var report = "_"+ team + "_<br />"; report += "___________<br />"; Object.keys(teamsTmp[team].members).forEach(function(member) { report += "**User:** " + member + "<br />"; report += "**What did " + member + " do yesterday:** " + teamsTmp[team].members[member].q1 + "<br />"; report += "**What will " + member + " do today:** " + teamsTmp[team].members[member].q2 + "<br />"; report += "**Impediments for " + member + ":** " + teamsTmp[team].members[member].q3 + "<br />"; report += "___________<br />"; }); return report; } 

Для форматирования сообщений вы можете использовать уценку.

Добавьте его перед всем, перед строкой bot.use(builder.Middleware.firstRun ...

Обратите внимание, что в конце ежедневного диалогового окна scrum мы добавляем еще одну функцию с помощью setInterval() , которая, когда первый член команды будет завершен, начнет отслеживать, все ли остальные члены команды закончили с ответом. Когда все готово, он начинает новый диалог с каждым членом команды и отправляет им сгенерированный отчет, который мы добавим как наш последний путь диалога.

 bot.dialog("/report", function(session, args) { session.send(args.report); session.endDialog(); }); 

Обратите внимание, что мы передаем отчет в качестве аргумента функции начала диалога, а затем снова можем прочитать его из параметра args вызываемого диалога.

демонстрация

Пришло время попробовать это. Я рекомендую вам выйти и перезапустить эмулятор и скрипт бота, чтобы убедиться, что пользовательские данные сбрасываются и выполняется последний код из скрипта.

Кроме того, измените время схватки в файле JSON, чтобы убедиться, что собрание инициировано, а не ждать следующего сохраненного ранее времени.

Попробуйте что-нибудь сказать боту, и он попросит вас указать название вашей команды.

демо # 1

Может случиться, что время запуска scrum «прошло» при запуске эмулятора или чего-то подобного, поэтому, если эмулятор не запрашивает у вас вопросы сразу, задайте время (либо непосредственно в файле JSON, либо через конфигуратор Electron) до 0, и это заставит бота начать еще одну встречу на сегодня.

Как только вы измените это, вам будет предложено ежедневное диалоговое окно с 3-шаговым водопадом.

демо # 2

Чтобы попробовать это с более чем одним пользователем, мы должны были бы развернуть это на сервере, который может обслуживать через SSL, так как это требование Microsoft Bot Directory.

Следующие шаги

Мы только поцарапали поверхность того, что возможно с MBF. Ниже приведены несколько вещей, которые заслуживают дополнительного исследования, чтобы поднять ваших ботов на следующий уровень.

ЛУИС

Microsoft Bot Framework предлагает гораздо больше, чем это. Некоторые интересные вещи включают LUIS (Language Understanding Intelligent Service) , который использует данные, полученные от Cortana и BING, для создания ИИ, который пытается понять, что хочет сказать пользователь.

Намеренные диалоги

Несколько более простым примером являются диалоговые окна намерений, которые похожи на обычные диалоговые окна, которые мы использовали, но вместо маршрута у них есть регулярное выражение в качестве первого параметра. Основываясь на регулярном выражении, вы можете попытаться обнаружить НАМЕРЕНИЕ пользователя и сделать некоторые конкретные вещи для распознанного намерения. Например:

 // example from https://docs.botframework.com/en-us/node/builder/chat/IntentDialog/ var intents = new builder.IntentDialog(); bot.dialog("/", intents); intents.matches(/^echo/i, [ function (session) { builder.Prompts.text(session, "What would you like me to say?"); }, function (session, results) { session.send("Ok... %s", results.response); } ]); 

Я нашел очень полезные примеры репозиториев, предоставленных Microsoft:

https://github.com/Microsoft/BotBuilder-Samples

Это все люди

Мы рассказали об основах Electron, Scrum, стеке диалогового окна платформы ботов, типе водопадного диалога, промежуточном программном обеспечении для передачи сообщений и о том, как случайным образом инициировать диалог с пользователем без первоначального запроса от пользователя.

Спасибо, что следовали этому уроку. В будущем мы увидим все больше и больше чат-ботов (надеюсь, не слишком много ?!). Если у вас есть какие-либо комментарии, предложения или вопросы, пожалуйста, оставьте комментарий ниже.

Эта статья была рецензирована Вильданом Софтиком и Камило Рейесом . Спасибо всем рецензентам SitePoint за то, что сделали контент SitePoint как можно лучше!