Мессенджеры
Messenger — системный модуль, содержащий классы, свойства, действия и формы для обмена сообщениями с внешними мессенджерами: учётные записи, через которые подключается приложение, чаты, в которых участвует учётная запись, хранимые сообщения, а также абстрактные действия отправки и синхронизации чатов, которые реализует модуль мессенджера. Модули Telegram, Viber, Whatsapp и Slack подключают одноимённые мессенджеры: приложение подключает нужные ему (REQUIRE Telegram), и вместе с каждым из них подключается Messenger (а с ним Reflection и Utils). Модуль Skype из той же папки рассчитан на платформу ботов Skype, которую Microsoft закрыла в 2025 году, и здесь не описывается.
Мессенджеры
Messenger — класс мессенджеров. Каждый модуль мессенджера расширяет его статическим объектом своего мессенджера — Messenger.telegram, Messenger.viber, Messenger.whatsapp, Messenger.slack — и добавляет свои реализации в описанные ниже абстрактные свойства и действия. Приём каждый модуль реализует самостоятельно.
| Модуль | Настройки учётной записи | Приём | Отправка |
|---|---|---|---|
Telegram | token[Account] — токен бота | по адресу доставки, регистрируемому при создании учётной записи, если не задано skipSetWebHookTelegram[Account]. Личные чаты и группы | текст и файлы |
Viber | token[Account] — токен аутентификации бота | по адресу доставки, регистрируемому при создании учётной записи. Личный чат создаётся, когда пользователь подписывается или пишет, и удаляется, когда он отписывается | текст |
Whatsapp | accessToken[Account] — временный токен доступа, phoneNumberId[Account] — идентификатор номера телефона отправителя. Общий для приложения verifyToken[] должен совпадать с токеном подтверждения, введённым вместе с адресом | по адресу доставки webhookUrl[], вводимому вручную в настройках разработчика Meta. Личные чаты | текст и файлы. sendTemplateMessage[Chat] отправляет шаблон hello_world, не сохраняя сообщение |
Slack | token[Account] — пользовательский токен доступа к API | по запросу: synchronizeChats[Account] загружает пользователей и каналы, а receiveMessages[Chat] — сообщения чата, пришедшие после последнего сохранённого | текст |
Учётные записи, чаты и сообщения
Account хранит настройки подключения к одному мессенджеру.
| Свойство | Что хранит |
|---|---|
name[Account] | название учётной записи |
messenger[Account] | мессенджер как объект Messenger. captionMessenger[Account] возвращает его заголовок |
token[Account] | токен API мессенджера |
account[LONG] находит учётную запись по идентификатору её объекта: адреса доставки Telegram и Viber передают его параметром botid.
Chat — переписка учётной записи с одним пользователем, группой или каналом.
| Свойство | Что хранит |
|---|---|
account[Chat] | учётная запись. При её удалении удаляются и её чаты |
id[Chat] | идентификатор чата в мессенджере |
chatType[Chat] | вид чата как объект ChatType: ChatType.private, ChatType.group или ChatType.channel. captionChatType[Chat] возвращает его заголовок |
name[Chat], title[Chat] | имя (имя пользователя или канала) и заголовок чата. Заголовок показывается для мессенджеров, у которых задано showTitle[Messenger] |
chatIdAccount[Account, STRING] находит чат по учётной записи и идентификатору. Чат создаётся, когда приходит его первое сообщение (в Viber — также когда пользователь подписывается). Мессенджер, который перечисляет свои чаты (Slack, у него задано isSynchronize[Messenger]), загружает их действием synchronizeChats[Account] — оно выполняется при создании учётной записи и по кнопке synchronizeChatsAction[Account] формы.
Message — сохранённое сообщение чата.
| Свойство | Что хранит |
|---|---|
chat[Message] | чат |
message[Message] | текст |
file[Message] | файл, отправленный из приложения действием sendFile[Chat, NAMEDFILE]. Принимающие действия входящие файлы не сохраняют. openFile[Message] открывает его |
from[Message] | отправитель: в Telegram — имя пользователя, а без него имя; в Viber — имя подписавшегося пользователя или отправителя сообщения; в Whatsapp — номер телефона; в Slack — имя пользователя, найденного среди загруженных, или YOU, если он не найден. YOU также помечает сообщение, отправленное из приложения |
dateTime[Message] | момент сохранения сообщения. Для сообщения, принятого Slack, — время сообщения в мессенджере |
Отправка
sendMessage[Chat, STRING] отправляет в чат текст, а sendFile[Chat, NAMEDFILE] — файл. Оба — абстрактные действия условной формы: модуль мессенджера добавляет реализацию для чатов своих учётных записей. Отправленное сообщение сохраняется как Message с YOU в from[Message]. Затем реализация применяет изменения текущей сессии вместе с остальными накопленными в ней изменениями, независимо от того, сообщил ли мессенджер об ошибке. Неудавшийся запрос (нет соединения или в ответе статус ошибки) завершает действие ошибкой. Ошибку, которую мессенджер сообщает в успешном ответе, в локальное свойство sendMessageResult[] записывают Telegram, Viber и Slack, а Whatsapp ответ не проверяет. Действие формы sendMessage[Chat] отправляет введённый текст newMessage[Chat] и, если это прошло без ошибки, вложенный файл newAttachment[Chat], а затем показывает ошибку, если она есть. Viber и Slack файлы не отправляют: у них sendFile[Chat, NAMEDFILE] только записывает предупреждение в sendMessageResult[].
Приём
Telegram, Viber и Whatsapp доставляют входящие сообщения сами: мессенджер отправляет HTTP-запрос веб-серверу приложения по адресу доставки, зарегистрированному для учётной записи. Адрес складывается из webServerUrl[] модуля Reflection и имени действия, принимающего запрос: <webServerUrl>/exec/webHookTelegram?botid=<идентификатор учётной записи> для Telegram, то же с webHookViber для Viber и <webServerUrl>/exec/webHookWhatsapp для Whatsapp, который определяет учётную запись по идентификатору номера телефона в запросе (phoneNumberId[Account]). Принимающие действия webHookTelegram[JSONFILE], webHookViber[JSONFILE] и webHookWhatsapp[JSONFILE] объявлены с @@noauth, поэтому мессенджер вызывает их из внешней системы без аутентификации. Каждое из них читает сообщение из тела запроса, находит или создаёт чат, сохраняет Message и применяет изменения, а Whatsapp вместо этого отвечает на запрос подтверждения адреса (запрос без тела), если его токен совпадает с verifyToken[]. Веб-сервер должен быть доступен мессенджеру по этому адресу по HTTPS, которого мессенджеры требуют. Без такого адреса учётная запись Telegram может только отправлять — с отключённой регистрацией, как описано ниже, — а учётную запись Viber создать нельзя вовсе.
Telegram и Viber регистрируют адрес доставки сами при создании учётной записи (setWebHookTelegram[Account], setWebHookViber[Account], выполняемые событием на новой учётной записи): если мессенджер отклоняет регистрацию, показывается его сообщение, а создание отменяется. Для учётной записи Telegram, которая только отправляет, регистрацию отключает реализация абстрактного свойства skipSetWebHookTelegram[Account]. Для Whatsapp адрес webhookUrl[] вводится в собственных настройках мессенджера вместе с токеном подтверждения, с которым должен совпадать verifyToken[].
Slack вместо этого принимает своими запросами и адреса доставки не требует: receiveMessages[Chat] запрашивает у мессенджера сообщения чата, пришедшие после последнего сохранённого. Оно выполняется по кнопке формы и перед отправкой текста.
Формы
Форма messengers, размещённая в папке навигатора notification, показывает учётные записи, чаты выбранной учётной записи с полями нового сообщения и вложения, а также сообщения выбранного чата. Форма перечитывает данные каждые 20 секунд, показывая сообщения, доставленные приложению за это время. Сообщения Slack загружает только receiveMessages[Chat]. Форма messenger — форма редактирования учётной записи. Модули мессенджеров добавляют в эти формы свои настройки и действия.
Примеры
MODULE Notifications;
REQUIRE Telegram;
CLASS Order 'Заказ';
number 'Номер' = DATA STRING[20] (Order);
// учётная запись только отправляет: признак ставится на её форме до сохранения,
// и адрес доставки для неё не регистрируется
sendOnly 'Только отправка' = DATA BOOLEAN (Account);
skipSetWebHookTelegram(Account a) += sendOnly(a);
EXTEND FORM messenger PROPERTIES(a) sendOnly;
// уведомление во все чаты Telegram с именем operator: такой чат появляется, когда этот пользователь пишет учётной записи,
// либо создаётся вручную с учётной записью, идентификатором чата Telegram в id[Chat] и именем в name[Chat]
notifyOperator 'Уведомить оператора' (Order o) {
FOR isTelegram(account(Chat c)) AND name(c) = 'operator' DO
sendMessage(c, 'Заказ ' + number(o) + ' готов');
}