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



Практически все, кто пользуется современными средствами обмена сообщениями, сталкивались с ботами. Одно из определений бота – это программа, выполняющая автоматически и/или по заданному расписанию какие-либо действия через интерфейсы, предназначенные для людей.

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

Бот для Telegram


Самая лёгкая платформа для разработки – это Telegram. Процесс начинается с вызова специального бота BotFather.



Всё очень просто, в строке поиска вбиваете BotFather, нажимаете кнопку start, выбираете команду \newbot, после чего, последовательно отвечая на вопросы, указываете имя бота и его пользовательское имя. В финале Telegram сообщит, что бот успешно создан, предоставит его ключ и предложит указать дополнительное описание и/или ввести названия команд.

Технически у вас два способа, чтобы обработать сообщения и команды бота. Первый заключается, в том, что ваш сервис периодически опрашивает сервер Telegram на наличие изменений. Для демонстрации воспользуемся API Telegram.Bot (пакет доступен через nuget).

using Telegram.Bot;
using Telegram.Bot.Args;
using Telegram.Bot.Types;
using Telegram.Bot.Types.Enums;
class Program
{
	private static TelegramBotClient client;	
	static void Main(string[] args)
	{
// token, который вернул BotFather
		client = new TelegramBotClient(token);
		client.OnMessage += BotOnMessageReceived;
		client.OnMessageEdited += BotOnMessageReceived;
		client.StartReceiving();
		Console.ReadLine();
		client.StopReceiving(); 
	} 
private async void BotOnMessageReceived(object sender, MessageEventArgs messageEventArgs)
{
var message = messageEventArgs.Message;         
if (message?.Type == MessageType.TextMessage)
{
await client.SendTextMessageAsync(message.Chat.Id, message.Text);
}
} 
}

В примере выше каждое входящее сообщение повторно отправляется пользователю. Сразу отмечу, что в Telegram команда – это тоже сообщение, только со знаком “/”.

Второй способ требует настройки webhook, т.е. сервиса, размещённого по https-адресу, который будет обрабатывать изменения. Этот способ меньше нагружает сервера Telegram, но требует наличия сертификата. Впрочем, проблема с сертификатом легко решается благодаря сервису Ngrok (https://ngrok.com/), который может осуществлять туннелирование запросов с https-адреса на адрес приложения на вашей машине.

Чтобы воспользоваться Ngrok, необходимо:

  • зарегистрироваться на сайте и получить персональный ключ;
  • установить ngrok.exe и в командной строке ввести ngrok authtoken ключ Ngrok;
  • выполнить команду ngrok http портсервиса;
  • в Visual Studio можно поставить плагин Ngrok и воспользоваться командой из меню Tools -> Start Ngrok tunnel .

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



HTTPS-адрес, который вернёт Ngrok, будет меняться при каждом запуске туннелирования, но вам не нужно делать это всё время при отладке приложения. Настройте туннелирование для конкретного порта один раз и останавливайте (запускайте) ваш сервис сколько угодно раз.

У Ngrok есть административная страница, доступная по адресу 127.0.0.1:4040, на которой можно посмотреть параметры запросов пользователя.



Вернёмся к нашему предыдущему примеру с повторной отправкой сообщения и создадим ASP.NET Core проект со следующим WebAPI-контроллером.

[Route("bot")] 
public class BotController : Controller 
{ 
// token, который вернул BotFather
private readonly TelegramBotClient client = new TelegramBotClient(token);
[HttpPost]
 public async void Post([FromBody]Update update)
{
if (update == null) return;
var message = update.Message;
if (message?.Type == MessageType.TextMessage)
{
	await client.SendTextMessageAsync(message.Chat.Id, message.Text);
}
}
} 

Для того чтобы сообщить Telegram, где находится сервис, можно написать следующее простое консольное приложение.

using Telegram.Bot;
using Telegram.Bot.Args;
using Telegram.Bot.Types;
using Telegram.Bot.Types.Enums;
class Program
{
	static void Main(string[] args)
	{
// token, который вернул BotFather
		var client = new TelegramBotClient(token);
		client.SetWebhookAsync("https адрес от Ngrok сервиса").Wait(); 
		Console.ReadLine();
		client.SetWebhookAsync().Wait();
	} 
}

Бот для Slack


Рассмотрим другой популярный сервис обмена сообщениями. Создание бота в Slack начинается с простой формы (https://api.slack.com/apps/new), где нужно заполнить название приложения и указать рабочее пространство (workspace).

После нажатия кнопки Create App появится страница настройки, где в подразделе Add features and functionality вам нужно выбрать пункт Slash Commands, чтобы перейти к созданию команд бота. На появившейся форме нужно будет ввести:

  • В поле Command — название команды
  • В поле Request URL – https-адрес нашего сервиса
  • В поле Short Description – назначение команды



На рисунке пример, как в чате выглядит вызов команды getmoney бота AlfaTestNdo.



После выбора команды Slack по указанному https-адресу отправляет следующее сообщение

public class Message 
{
        public string channel_id { get; set; }
        public string channel_name { get; set; }
        public string command { get; set; }
        public string response_url { get; set; }
        public string team_domain { get; set; }
        public string team_id { get; set; }
        public string text { get; set; }
        public string token { get; set; }
        public string trigger_id { get; set; }
        public string user_id { get; set; }
        public string user_name { get; set; }
}

Посмотрим теперь, как будет выглядеть код нашего сервиса, отправляющего в ответ сообщение “hello”.

[Route("bot")]
public class BotController : Controller
{
        [HttpPost]
        public async void Post(Message message)
        {
	var uri = new Uri("https://slack.com/api/chat.postMessage?token=" 
	+ token + "&channel=“ + message.channel_id + "&text=hello"); 
	var httpClient = new HttpClient(); 
	await httpClient.GetAsync(uri).ConfigureAwait(false);
        }
}

В сервисе вызывается метод chat.postMessage с ключом token и идентификатором канала message.channel_id.

Slack определяет следующие типы ключей:

  • User token — ключ пользователя, установившего приложение, при этом применяется авторизация через Oauth. Имеет префикс xoxp. Подробнее об этом здесь https://api.slack.com/docs/oauth
  • Bot user token – ключ, технического виртуального пользователя. Имеет префикс xoxb. Подробнее о них написано здесь https://api.slack.com/bot-users.
  • Workspace token – ключ рабочей области. Имеет префикс xoxa.
  • Legacy token – ключ авторизованного пользователя. Имеет префикс xoxp. Подробнее о них здесь https://api.slack.com/custom-integrations/legacy-tokens. Не рекомендуется их использовать.
  • Verification token – этот ключ slack отправляет при обращению к вашему сервису. С помощью него можно проверить, что именно ваше приложение обращается к сервису. Никогда не применяется для вызовов методов API.

Все методы API Slack хорошо документированы, содержат информацию о поддерживаемых ключах. На странице описания метода также есть вкладка Tester для тестирования.

https://api.slack.com/methods/chat.postMessage



В завершении темы Slack добавлю, что как и в Telegram вы можете создавать не только текстовые сообщения, но и различные интерактивные компоненты: кнопки, меню и так далее.

Бот для Facebook


Создание бота в Facebook, на мой взгляд сложнее, чем в Telegram и Slack, но в целом почти всё то же самое. Вам нужно зайти на страницу developers.facebook.com/apps, нажать кнопку “Добавить новое приложение” и ввести название, а также адрес почты для обратной связи.



После нажатия кнопки “Создайте ID приложения” откроется страница настройки, где нужно указать персональную страницу бота (потребуется создать её), чтобы получить ключ доступа к методам API.



В разделе Webhook потребуется указать https-адрес вашего сервиса, придумать проверочный ключи и ввести его в поле “Подтвердить маркер”, а также выбрать информацию, на которую вы подписываетесь.



Тут важно знать следующее, при нажатии кнопки “Подтвердить и сохранить” Facebook отправит проверочное сообщение с полями hub.mode, hub.challenge плюс hub.verify_token, в котором будет ваш персональный ключ. Ваш сервис должен суметь принять это сообщение и вернуть hub.challenge в качестве успеха, если в поле hub.verify_token был действительно передан ваш ключ.

  [Route("bot")]
    public class BotController : Controller
    {
[HttpGet]
public string Verify()
{
            var mode = Request.Query["hub.mode"].FirstOrDefault();
            var challenge = Request.Query["hub.challenge"].FirstOrDefault();
            var token = Request.Query["hub.verify_token"].FirstOrDefault();
            return challenge ?? string.Empty;
}
    }
 

Допустим, вы подписались только на messages и отправили боту сообщение “hello”. В этом случае на указанный вами https-адрес Facebook отправит сообщение следующего вида.



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

  [Route("bot")]
    public class BotController : Controller
    {        
        	[HttpPost]
public void Post([FromBody] Letter letter)
{
            var content = letter.entry[0].messaging[0];
            const string token = “сгенерированный ключ, когда вы указали страницу бота";
            var uri = new Uri("https://graph.facebook.com/v2.6/me/messages?access_token=" + token);
             
            var request = (HttpWebRequest)WebRequest.Create(uri);
            request.ContentType = "application/json";
            request.Method = "POST";
            using (var requestWriter = new StreamWriter(request.GetRequestStream()))
            {
                requestWriter.Write($@" {{recipient: {{  id: {content.sender.id}}},message: {{text: ""{content.message.text}"" }}}}");
            }
            var response = (HttpWebResponse)request.GetResponse();
} 
    } 

Bot Framework


Наш обзор будет неполным, если мы не коснёмся такого замечательного инструмента, как Bot Framework от компании Microsoft.

Во-первых, Bot Framework предоставляет унифицированное API для работы с разными каналами: Bing, Cortana, Email, Facebook, GroupMe, Kik, Skype, Skype for Business, Slack, SMS, Microsoft Teams.

Во-вторых, вы получаете эмулятор (https://github.com/Microsoft/BotFramework-Emulator) для тестирования вашего сервиса, который не требует настройки https-адреса. Эмулятор в том числе позволяет проверить уже размещённый сервис по адресу dev.botframework.com/bots, хотя в этом случае потребуется указать ID и пароль приложения.



Есть, правда, и ряд неприятных моментов. В частности, нет поддержки .Net Core. Об этом подробнее написано здесь.
github.com/Microsoft/BotBuilder/issues/572
designprincipia.com/microsoft-bot-framework-on-asp-net-core

Вернёмся к нашему примеру с повторной отправкой сообщения. Вам потребуется установить шаблон Bot Framework.



В данном примере в ответ на текстовое сообщение от пользователя (тип ActivityTypes.Message) создаётся экземпляр класса, поддерживающего интерфейс IDialog.

 [Route("api/[controller]")]
    [BotAuthentication]
    public class MessagesController : Controller
    {
        [HttpPost]
        public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
        {
            if (activity?.Type == ActivityTypes.Message)
            {
                await Conversation.SendAsync(activity, () => new Dialogs.RootDialog());
            }
            return new HttpResponseMessage(HttpStatusCode.OK);
        }        
    } 

В нашем случае это класс RootDialog с методом StartAsync, который организует задачу по отправке входящего сообщения.

[Serializable]
public class RootDialog : IDialog<object>
{
	public Task StartAsync(IDialogContext context)
	{
		context.Wait(MessageReceivedAsync);
		return Task.CompletedTask;
	}
	private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<object> result)
	{
		var activity = await result as Activity;
		await context.PostAsync(activity.Text);
		context.Wait(MessageReceivedAsync);
	}
}
 

На мой взгляд, самой интересной фишкой Bot Framework является Connector. Это API, позволяющее организовать связь между разными каналами. Более подробно можно почитать здесь: docs.microsoft.com/en-us/bot-framework/dotnet/bot-builder-dotnet-connector

Заключение


Если сравнивать процессы разработки бота и web-приложения, можно отметить следующее: в обоих случаях одинаково требуется middle-слой обработки запросов, но что касается front-части – различие существенное. Когда вы делаете бота, вам не нужно искать ресурсы, чтобы разработать интерфейс, способный работать в разных браузерах и операционных системах, т.к. за интерфейс отвечает разработчик сервиса обмена сообщений. И хотя вы никак не контролируете API, во многих случаях реализация взаимодействия с пользователем через бот вместо сайта может оказаться перспективнее и сэкономить много ресурсов.

Полезные ссылки


Официальные инструкции



SDK



Дополнительные инструкции


Комментарии (12)


  1. Tantrido
    04.12.2017 16:14

    Есть, правда, и ряд неприятных моментов. В частности, нет поддержки .Net Core. Об этом подробнее написано здесь.
    github.com/Microsoft/BotBuilder/issues/572
    Уже есть: github.com/Microsoft/BotBuilder/issues/572#issuecomment-348195401 — хотел статью написать или перевод по теме, но ты меня опередил :)


  1. BosonBeard
    04.12.2017 21:25
    +1

    Хорошая статья, ботами никогда не интересовался, но бегло пролистал и вроде как концептуально все понятно, даже захотелось когда-нибудь опробовать, продолжайте в том же духе!


  1. dmitry_dvm
    04.12.2017 23:56

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


    1. wrmax Автор
      05.12.2017 00:50
      +1

      В целевом варианте да. Сервис в любом случае надо будет размещать на каком-либо хосте, хоть azure. Ngrok приведён в статье, потому что на мой взгляд он проще для начала работы.


      1. dmitry_dvm
        05.12.2017 10:55
        +1

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


    1. Ti_Fix
      05.12.2017 11:54

      Разве хостинг в Azure бесплатен (не считая студентов)?


      1. bluetooth
        05.12.2017 15:52

        Не только студентов. Все, у кого есть подписка MSDN (или как она сейчас называется). Да и остальные могут подписаться на бесплатные 30 дней.


        1. NeuroHunter
          05.12.2017 16:13

          Плюс — для бота можно взять бесплатную подписку с определенным количеством API calls. Конечно для промышленного внедрения это не прокатит, но для себя любимого — вполне


          1. Oxoron
            05.12.2017 20:18

            Можно ссылку?


            1. NeuroHunter
              05.12.2017 20:53

              azure.microsoft.com/en-us/pricing/details/bot-service
              Для Standard Web App есть бесплатный вариант.
              Раньше был бесплатный вариант и для Serverless, но теперь, как я погляжу, его убрали.


      1. dmitry_dvm
        05.12.2017 21:57

        У меня бот живет там как веб-приложение с оплатой по мере использования. Трафик не ахти какой, в среднем 10к запросов в месяц, до минимального платного порога не дотягивает. С момента анонса телеграм-ботов крутится бесплатно.


  1. stDistarik
    05.12.2017 10:52
    +1

    Можно самопаканый сертификат прикрутить, а можно nginx + Let’s Encrypt использовать в качестве прокси для любого количества ботов.

    Вот здесь писал своего бота на СИ.