Функционал из данного гайда был перенесен на messengers Business API, с помощью которого вы также сможете производить рассылки, создавать чат боты и многое другое, но без риска быть заблокированным.
В связи с изменением условий работы API, в данном гайде частично могут использоваться неактуальные сведения. Пожалуйста, посмотрите документацию перед началом работы.
Сейчас Chat API предлагает самый доступный и автоматизированный messengers Business API на рынке с Многопользовательским Чатом, Визуальным конструктором ботов, готовыми интеграциями приложений и другими полезными функциями.
В этом руководстве расскажем как создать messengers бота на C#, используя наш шлюз API messengers
Писать наш бот будем с использованием технологии ASP.Net для поднятия сервера, который будет обрабатывать и отвечать на запросы пользователей
Данный бот будет реагировать на команды, поступающие ему в виде обычных сообщений в messengers и отвечать на них. Функционал тестового бота будет ограничен следующими функциями:
Какой функционал у данного бота?
- Реакция приветственным текстом на сообщение, команды которого нет у бота. Вывод меню бота
- Вывод ID текущего чата (в ЛС или в беседе)
- Отправка файлов различных форматов (pdf, jpg, doc, mp3 и др.)
- Отправка голосовых сообщений (файлов *.ogg)
- Отправка геолокации
- Создание отдельной группы с собеседником и ботом
Глава 1. Создание проекта ASP.Net
Откроем Visual Studio и создадим проект "Веб приложение ASP.NET Core".
Далее выберем шаблон с пустым проектом (также можно выбрать шаблон API, который будет включать в себя уже необходимые контроллеры, которые останется только отредактировать. Мы же для наглядности создадим все с нуля)
Откроем файл Startup.cs и впишем в метод Configure данный код:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
Это позволит нам настроить навигацию с использованием контроллера. Сейчас приступим к написанию самого контроллера.
Для этого создадим в проекте папку с названием Controllers, в которой создадим наш класс контроллер WebHookController
Наш контроллер должен наследовать класс ControllerBase и быть помечен атрибутами
using Microsoft.AspNetCore.Mvc;
namespace WaBot.Controllers
{
[ApiController]
[Route("/")]
public class WebHookController : ControllerBase
{
}
}
Атрибут Route отвечат за адрес, по которому будет срабатывать данный котроллер. Указываем базовый путь домена.
На данном этапе наш контоллер практически готов. Теперь нам необходимо добавить методы по работе с API WA и другие вспомогательные классы, которые пригодятся нам в работе.
Авторизация телефона
В самом начале, сразу свяжем messengers с нашим скриптом, чтобы по мере написания кода - проверять его работу. Для этого переходим в личный кабинет и получаем там QR-код. Далее открываем messengers на мобильном телефоне, заходим в Настройки -> messengers Web -> Сканируем QR-код.
Для того, чтобы наш бот реагировал на команды необходимо, чтобы бот мог принять и обработать входящие данные. Для этого требуется поднять собственный сервер - Webhook и указать его адрес в личном кабинете.
Немного о том, для чего нужен Webhook
Webhook решает проблему с задержкой на отклик входящих сообщений. Без него, нашему боту пришлось бы постоянно спрашивать у сервера о входящих данных, делать периодические фиксированные во времени запросы к серверам. Тем самым имея некоторую задержку в отклике, а также это способствовало бы нагрузке на сервер.
Но, если мы укажем адрес Webhook сервера, то данная необходимость перестанет быть актуальной. Сервера сами будут присылать уведомления о входящих изменениях, как только они появятся. А задача Webhook сервера их принять и правильно обработать, реализовывая логику бота. Указывать можно как домен, так и ip адрес
Глава 2. Класс API
В этой главе мы рассмотрим написание класса, который будет отвечать за взаимодействие с нашем API шлюзом. Документацию можно почитать здесь. Создадим класс WaApi:
public class WaApi
{
private string APIUrl = "";
private string token = "";
public WaApi(string aPIUrl, string token)
{
APIUrl = aPIUrl;
this.token = token;
}
}
Данный класс будет хранить в себе поля APIUrl и token, которые необходимы для работы с API. Получить их можно в вашем личном кабинете аккаунта.
Так же и конструктор, который присваивает значения в поля. Благодаря этому мы можем иметь несколько объектов, которые могут представлять различных ботов в случае, если необходимо настроить работу нескольких ботов одновременно.
Метод для отправки запросов
Добавим в этот класс асинхронный метод, который будет осуществлять отправку POST запросов:
public async Task<string> SendRequest(string method, string data)
{
string url = $"{APIUrl}{method}?token={token}";
using (var client = new HttpClient())
{
client.BaseAddress = new Uri(url);
var content = new StringContent(data, Encoding.UTF8, "application/json");
var result = await client.PostAsync("", content);
return await result.Content.ReadAsStringAsync();
}
}
Данный метод принимает два аргумента.
- method - название нужного метода согласно документации
- data - json строка для отправки
В методе сформируем строку url, на которую будет отправляться запрос. И далее делаем POST запрос на данный адрес с помощью класса System.Net.Http.HttpClient. Возвращаем ответ сервера.
На основе данного метода мы можем сделать необходимый нам функционал для работы бота.
Отправка сообщений
public async Task<string> SendMessage(string chatId, string text)
{
var data = new Dictionary<string, string>()
{
{"chatId",chatId },
{ "body", text }
};
return await SendRequest("sendMessage", JsonConvert.SerializeObject(data));
}
Данный метод в качестве параметров принимает:
- chatId - ID чата, куда необходимо отправить сообщение
- text - текст отправляемого сообщения.
Для того, чтобы сформировать строку Json, воспользуемся удобной библиотекой Newtonsoft.Json. Создадим словарь, ключом которого будет строка с необходимым Json полем, согласно документации, а значением - наши параметры. Достаточно просто вызвать метод JsonConvert.SerializeObject и передать в него наш словарь для формирования Json строки. Вызываем метод SendRequest, передав в него название метода для отправки сообщений и нашу json строку.
Таким образом наш бот будет отвечать пользователям.
Отправка голосового сообщения
public async Task<string> SendOgg(string chatId)
{
string ogg = "https://firebasestorage.googleapis.com/v0/b/chat-api-com.appspot.com/o/audio_2019-02-02_00-50-42.ogg?alt=media&token=a563a0f7-116b-4606-9d7d-172426ede6d1";
var data = new Dictionary<string, string>
{
{"audio", ogg },
{"chatId", chatId }
};
return await SendRequest("sendAudio", JsonConvert.SerializeObject(data));
}
Далее логика построения методов аналогична. Смотрим документацию и отправляем на сервер нужные данные, вызывая необходимые методы.
Для отправки голосового сообщения используется ссылка на файл формата .ogg и метод sendAudio
Метод для отправки геолокации
public async Task<string> SendGeo(string chatId)
{
var data = new Dictionary<string, string>()
{
{ "lat", "55.756693" },
{ "lng", "37.621578" },
{ "address", "Your address" },
{ "chatId", chatId}
};
return await SendRequest("sendLocation", JsonConvert.SerializeObject(data));
}
Создание группы
Создает конференцию, в которой будете вы и бот
public async Task<string> CreateGroup(string author)
{
var phone = author.Replace("@c.us", "");
var data = new Dictionary<string, string>()
{
{ "groupName", "Group C#"},
{ "phones", phone },
{ "messageText", "This is your group." }
};
return await SendRequest("group", JsonConvert.SerializeObject(data));
}
Метод для отправки файлов
Обговорим некоторые моменты, связанные с данным методом. Он принимает в параметры:
- chatId - ID чата
- format - формат файла, который необходимо отправить.
Для того, чтобы отправить файл, документация предусматривает несколько способов:
- Ссылка на файл, который нужно отправить
- Строка, которая является файлом, закодированным с помощью метода Base64.
Рекомендуется отправлять файлы с помощью второго способа, закодировав файлы в Base64 формат. В Главе 4 мы более подробно об этом расскажем. А сейчас стоит знать, что мы описали статический класс Base64String, в котором описали свойства, записав в них все тестовые файлы нужных форматов. В методе просто вызываем свойство нужного формата и передаем данную Base64 строку на сервер.
public async Task<string> SendFile(string chatId, string format)
{
var availableFormat = new Dictionary<string, string>()
{
{"doc", Base64String.Doc },
{"gif",Base64String.Gif },
{ "jpg",Base64String.Jpg },
{ "png", Base64String.Png },
{ "pdf", Base64String.Pdf },
{ "mp4",Base64String.Mp4 },
{ "mp3", Base64String.Mp3}
};
if (availableFormat.ContainsKey(format))
{
var data = new Dictionary<string, string>(){
{ "chatId", chatId },
{ "body", availableFormat[format] },
{ "filename", "yourfile" },
{ "caption", $"My file!" }
};
return await SendRequest("sendFile", JsonConvert.SerializeObject(data));
}
return await SendMessage(chatId, "No file with this format");
}
На этом базовый функционал нашего класса API описан. Теперь соединим наш контроллер из Главы 1 и API.
Глава 3. Обработка запросов.
Вернемся к контроллеру из первой главы. Опишем метод внутри контроллера, который будет обрабатывать post-запросы, приходящие на наш сервер от chat-api.com. Назовем метод Post (название может быть любым) и пометим его атрибутом [HttpPost], что будет означать реагирование на Post запросы:
[HttpPost]
public async Task<string> Post(Answer data)
{
return "";
}
Принимать наш метод будет класс Answer, который является десериализированным объектом из пришедшей к нам строки json. Для того, чтобы описать класс Answer, нам потребуется узнать, какой json будет к нам приходить.
Для этого можно воспользоваться удобным разделом "Тестирование" - "Симуляция Webhooka" в вашем личном кабинете
Справа мы можем видеть json тело, которое будет к нам приходить.
Воспользуемся сервисом конвертации json в C#. Либо опишем класс сами, ипользуя атрибуты библиотеки Newtonsoft.Json:
public partial class Answer
{
[JsonProperty("instanceId")]
public string InstanceId { get; set; }
[JsonProperty("messages")]
public Message[] Messages { get; set; }
}
public partial class Message
{
[JsonProperty("id")]
public string Id { get; set; }
[JsonProperty("body")]
public string Body { get; set; }
[JsonProperty("type")]
public string Type { get; set; }
[JsonProperty("senderName")]
public string SenderName { get; set; }
[JsonProperty("fromMe")]
public bool FromMe { get; set; }
[JsonProperty("author")]
public string Author { get; set; }
[JsonProperty("time")]
public long Time { get; set; }
[JsonProperty("chatId")]
public string chatId { get; set; }
[JsonProperty("messageNumber")]
public long MessageNumber { get; set; }
}
Теперь, когда у нас есть объектное представление пришедшего запроса, сделаем обработку его в контроллере.
Внутри контроллера создадим статическое поле, которым будет являться наш API ссылкой и токеном:
private static readonly WaApi api = new WaApi("https://eu115.chat-api.com/instance12345/", "123456789token");
В цикле метода проходимся по всем пришедшим к нам сообщениям и делаем проверку на то, что обрабатываемое сообщение не является нашим собственным. Это нужно для того, чтобы бот не зацикливался сам на себе. Если все же сообщение от самого себя - пропускаем:
[HttpPost]
public async Task<string> Post(Answer data)
{
foreach (var message in data.Messages)
{
if (message.FromMe)
continue;
}
}
Далее опишем switch, в который будем передавать получаемые команды. Применим к свойству Body метод Split(), чтобы разбить сообщение по пробелам. Передадим в switch первую команду, вызвав метод ToLower(), чтобы стиль написания команды не играл роли и обрабатывался одинаково:
[HttpPost]
public async Task<string> Post(Answer data)
{
foreach (var message in data.Messages)
{
if (message.FromMe)
continue;
switch (message.Body.Split()[0].ToLower())
{
case "chatId":
return await api.SendMessage(message.chatId, $"Your ID: {message.chatId}");
case "file":
var texts = message.Body.Split();
if (texts.Length > 1)
return await api.SendFile(message.chatId, texts[1]);
break;
case "ogg":
return await api.SendOgg(message.chatId);
case "geo":
return await api.SendGeo(message.chatId);
case "group":
return await api.CreateGroup(message.Author);
default:
return await api.SendMessage(message.chatId, welcomeMessage);
}
}
return "";
}
В case запишем все необходимые нам команды и будем вызывать методы из объекта нашего API, которые их реализауют.
А default будет обрабатывать команды, которых не существует. Для этого просто будем отправлять сообщение из меню бота пользователю.
messengers бот на C#
Наш бот готов. Он уже может отвечать и обрабатывать команды пользователя, остается только добавить его на хостинг и указать домен в качестве webhook в личном кабинете пользователя chat-api.com.
А в следующих главах расскажем о base64 и возможных ошибках, с которыми вы можете столкнуться.
Глава 4. Base64
Base64 - это стандарт кодирования данных. С помощью данного стандарта мы можем закодировать файлы в строку и передавать их таким образом.
В вашем личном кабинете доступен сервис, который поможет сгенерировать строку данного формата. Для написания этого бота необходимо было предоставить несколько статичных данных для теста, поэтому мы вставили полученные строки в вспомогательный класс и обращались к ним из кода.
Вам же может быть полезнее кодировать файлы "на лету".
Для генерации таких строк можно также воспользоваться встроенными средствами языка C#.
Глава 5. Публикация сервера.
Для установки сервера в качестве Webhook требуется этот самый сервер загрузить в Интернет. Для этого можно воспользоваться сервисами по предоставлению услуг хостинга, vps или vds серверов. Для тестирования бота мы воспользовались одним из популярных хостингов (reg.ru)
Необходимо выбрать и оплатить услугу, которая поддерживает технологию ASP.Net. Далее можно воспользоваться данной инструкцией от хостинга по публикации сервера на хостинг.
Возможные проблемы, с которыми вы можете столкнуться
- Невозможно соединиться с сервером хостинга для публикации своего сервера. Решение: обратиться в техподдержку и попросить включить Web Deploy для вашей услуги
- HTTP ERROR 500.0 Решение здесь