История проекта Dagaz прошла у вас перед глазами. Когда я только начинал писать про Zillions, главным возражением было то, что платформа эта платная и запускается только под Windows. Появился Dagaz — полностью бесплатный проект с открытой лицензией и общедоступным исходным кодом, игры которого можно запускать из браузера, даже на мобилках. Блокирующим фактором стало отсутствие адекватных ботов. Пришлось осваивать GarboChess и разрабатывать DagazServer, на котором игроки могли бы играть по сети друг с другом. Это стало большим шагом вперёд, но на сервере требуется авторизация, а логины и пароли — это как раз то, что забывается легче всего. Счастлив сообщить, что теперь их запоминать не надо.Разумеется, существуют разнообразные схемы восстановления забытых паролей с использованием почты и SMS, но я решил с ними не связываться. В основном потому, что пользователи не очень-то любят вводить на сайтах свои почтовые адреса и номера телефонов, справедливо опасаясь спама. Есть способ лучше! Заботу о сохранении учётных данных возьмёт на себя бот.
Напомню, какой технологический стек я использую. UI сервера разработан с использованием Angular, а бакенд построен на фреймворке NestJS и использует для хранения данных СУБД PostgreSQL. DagazBot разработан в том же ключе, разве что Nest не используется. Всё запускается под Node.js. Вопрос разработки Telegram-бота на этой платформе уже разбирался на Хабре ранее, поэтому подробно останавливаться на этом не буду, сосредоточившись на специфике своего проекта. Итак, что я хотел получить в результате:
- Компактное и не требовательное к ресурсам сервера приложение
- Надёжное хранение учётных данных для доступа на DagazServer
- Простая авторизация на DagazServer не требующая запоминания логинов и паролей
- Возможность работы с несколькими учётными записями DagazServer
- Уведомление пользователей об играх на сервере, ожидающих их хода
- Средство коммуникации пользователей с администраторами бота
Требование компактности заставило отказаться от иcпользования NestJS и последующего прикручивания UI на Angular-е в пользу простейшего приложения на JavaScript. Интеграция с DagazServer осуществляется по REST, с использованием библиотеки Axios. База данных у бота своя, отдельная от сервера, но тоже на PostgreSQL. Зависимости проекта следующие. Упрощённая схема данных выглядит как-то так:

Вкратце о назначении таблиц
server — Здесь перечислены сервера, с которыми работает бот: Telegram и DagazServer. Тип сервера определяется словарём server_type. В server_option хранится ключ для подключения бота к Telegram (разумеется, в репозитории и дампе этого ключа нет). Также, в записи для DagazServer есть два важных поля: в api хранится URL для интеграции с сервером, а url используется для переадресации запросов на сервер.
users (множественное число, чтобы не конфликтовать с зарезервированным словом PostgreSQL) — учётные записи пользователей в Telegram. Не повторяйте моих ошибок, поле from.username из сообщений, хотя и является уникальным, ключём служить не может, поскольку в профилях пользователей Telegram присутствует далеко не всегда. Когда я это понял, в БД уже было несколько пользователей и мне пришлось подставлять from.id при отсутствии username-а. Кстати, к сведению параноиков: номер телефона, на который заведён Telegram в бот не передаётся!
account — Учётные записи на DagazServer (как я уже сказал, у одного Telegram-пользователя их может быть несколько). Вся конкретика с логинами и паролями хранится в user_param, там же сохраняются временные переменные, необходимые для работы скриптов, типы которых перечислены в param_type.
script — команды, которые можно выполнять на сервере (связь с сервером через табличку server_script). Составные кирпичики этих скриптов, action — отдельные действия, такие как: ввод и вывод строк, меню, обращение к хранимым процедурам в БД и REST-запросы.
common_context (не придумал лучшего названия) — та сущность, на которой выполняются action-ы. Первоначально всё это хранилось в users, но выяснилось, что могут быть кейсы, когда скрипты должны выполняться не на user-ах, а на account-ах. В command_queue очередь входящих команд (пока одна не отработала, другая не начнётся).
users (множественное число, чтобы не конфликтовать с зарезервированным словом PostgreSQL) — учётные записи пользователей в Telegram. Не повторяйте моих ошибок, поле from.username из сообщений, хотя и является уникальным, ключём служить не может, поскольку в профилях пользователей Telegram присутствует далеко не всегда. Когда я это понял, в БД уже было несколько пользователей и мне пришлось подставлять from.id при отсутствии username-а. Кстати, к сведению параноиков: номер телефона, на который заведён Telegram в бот не передаётся!
account — Учётные записи на DagazServer (как я уже сказал, у одного Telegram-пользователя их может быть несколько). Вся конкретика с логинами и паролями хранится в user_param, там же сохраняются временные переменные, необходимые для работы скриптов, типы которых перечислены в param_type.
script — команды, которые можно выполнять на сервере (связь с сервером через табличку server_script). Составные кирпичики этих скриптов, action — отдельные действия, такие как: ввод и вывод строк, меню, обращение к хранимым процедурам в БД и REST-запросы.
common_context (не придумал лучшего названия) — та сущность, на которой выполняются action-ы. Первоначально всё это хранилось в users, но выяснилось, что могут быть кейсы, когда скрипты должны выполняться не на user-ах, а на account-ах. В command_queue очередь входящих команд (пока одна не отработала, другая не начнётся).
Вся работа с БД и REST вынесена в service.js, в index.js взаимодействие с Telegram. Обратите внимание на deleteMessage. Из чата удаляются использованные меню (поскольку повторный выбор их пунктов ни к чему хорошему не приведёт), а также введённые пароли (или любые другие параметры, тип которых помечен как is_hidden). Помимо callback-ов, отрабатывающих получение от Telegam текста и пунктов меню, имеется две функции, периодически выполняющихся по таймеру.
Функция run выполняется достаточно часто, чтобы обеспечить приемлемое время отклика бота, но е��ли нет данных для обработки её выполнение приостанавливается. Функция schedule выполняется реже и запрашивает с DagazServer-а данные об ожидании ответного хода. Получение данных от Telegram (и от DagazServer) инициирует возобновление выполнения run.
В этом месте я словил состояние гонок
Асинхронные конструкции async/await создают иллюзию последовательного выполнения однопоточного кода. На самом деле это не так, любой await (при обращении к БД, например) приостанавливает выполнение и функция run может быть вызвана повторно. В этом случае начинаются всякие труднообъяснимые чудеса и чтобы их не было в код добавлен флаг isProcessing.
Разумеется, в DagazServer были добавлены новые эндпойнты, а чтобы работал редирект с авторизацией пришлось доработать и UI тоже. DagazServer создаёт одноразовые тикеты по запросу бота (поскольку оба они выполняются на одном сервере, пароли никуда дальше loopback-а не улетают). В app-routing был добавлен новый маршрут, а в компонент авторизации дополнительный код, использующий тикет, полученный из url, для авторизации.
Как всем этим пользоваться
Заходим в бот и жмём кнопку 'START':

Выбираем подключение к учётной записи DagazServer или создание новой (если что-то пойдёт не так, не страшно, команду '/start' всегда можно будет вызвать позднее через меню).

Далее бот запрашивает логин, пароль и EMail, причём пароль удаляется из чата сразу же после ввода. Для входа на DagazServer выполните команду '/enter':

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

Выбираем подключение к учётной записи DagazServer или создание новой (если что-то пойдёт не так, не страшно, команду '/start' всегда можно будет вызвать позднее через меню).

Далее бот запрашивает логин, пароль и EMail, причём пароль удаляется из чата сразу же после ввода. Для входа на DagazServer выполните команду '/enter':

… и просто перейдите по ссылке. Похожие ссылки бот будет присылать, по собственной инициативе, для входа в партии, ожидающие вашего хода.
Отдельно стоит упомянуть о переписке администраторов с пользователями. Любое сообщение администратора ретранслируется всем пользователям имеющим тот же язык локали, а сообщения пользователей передаются всем администраторам. Пока что обрабатываются только текстовые сообщения, но есть возможность ответа на сообщение (и над её реализацией пришлось поломать голову).

Любое сообщение в чат бота (message) ретранслируется нескольким получателям. Id этих сообщений фиксируются в таблице client_message и ответ будет осуществляться на эти сообщения. Получив такой ответ, надо найти id сообщения инициировавшего то сообщение, на которое выполнен ответ и отвечать уже на него. Сейчас всё выглядит довольно просто, но я потратил около получаса, чтобы отладить это.
Итак, я разработал бота, облегчающего жизнь пользователям DagazServer-а и поделился с вами историей его создания. Разумеется, я не собираюсь останавливаться на достигнутом и буду расширять его функциональность, но это всё уже в следующем году, а пока…
Всех с наступающим Новым Годом!