История проекта Dagaz прошла у вас перед глазами. Когда я только начинал писать про Zillions, главным возражением было то, что платформа эта платная и запускается только под Windows. Появился Dagaz — полностью бесплатный проект с открытой лицензией и общедоступным исходным кодом, игры которого можно запускать из браузера, даже на мобилках. Блокирующим фактором стало отсутствие адекватных ботов. Пришлось осваивать GarboChess и разрабатывать DagazServer, на котором игроки могли бы играть по сети друг с другом. Это стало большим шагом вперёд, но на сервере требуется авторизация, а логины и пароли — это как раз то, что забывается легче всего. Счастлив сообщить, что теперь их запоминать не надо.
Разумеется, существуют разнообразные схемы восстановления забытых паролей с использованием почты и SMS, но я решил с ними не связываться. В основном потому, что пользователи не очень-то любят вводить на сайтах свои почтовые адреса и номера телефонов, справедливо опасаясь спама. Есть способ лучше! Заботу о сохранении учётных данных возьмёт на себя бот.
Напомню, какой технологический стек я использую. UI сервера разработан с использованием Angular, а бакенд построен на фреймворке NestJS и использует для хранения данных СУБД PostgreSQL. DagazBot разработан в том же ключе, разве что Nest не используется. Всё запускается под Node.js. Вопрос разработки Telegram-бота на этой платформе уже разбирался на Хабре ранее, поэтому подробно останавливаться на этом не буду, сосредоточившись на специфике своего проекта. Итак, что я хотел получить в результате:
Требование компактности заставило отказаться от иcпользования NestJS и последующего прикручивания UI на Angular-е в пользу простейшего приложения на JavaScript. Интеграция с DagazServer осуществляется по REST, с использованием библиотеки Axios. База данных у бота своя, отдельная от сервера, но тоже на PostgreSQL. Зависимости проекта следующие. Упрощённая схема данных выглядит как-то так:
Вся работа с БД и REST вынесена в service.js, в index.js взаимодействие с Telegram. Обратите внимание на deleteMessage. Из чата удаляются использованные меню (поскольку повторный выбор их пунктов ни к чему хорошему не приведёт), а также введённые пароли (или любые другие параметры, тип которых помечен как is_hidden). Помимо callback-ов, отрабатывающих получение от Telegam текста и пунктов меню, имеется две функции, периодически выполняющихся по таймеру.
Функция run выполняется достаточно часто, чтобы обеспечить приемлемое время отклика бота, но если нет данных для обработки её выполнение приостанавливается. Функция schedule выполняется реже и запрашивает с DagazServer-а данные об ожидании ответного хода. Получение данных от Telegram (и от DagazServer) инициирует возобновление выполнения run.
Разумеется, в DagazServer были добавлены новые эндпойнты, а чтобы работал редирект с авторизацией пришлось доработать и UI тоже. DagazServer создаёт одноразовые тикеты по запросу бота (поскольку оба они выполняются на одном сервере, пароли никуда дальше loopback-а не улетают). В app-routing был добавлен новый маршрут, а в компонент авторизации дополнительный код, использующий тикет, полученный из url, для авторизации.
Отдельно стоит упомянуть о переписке администраторов с пользователями. Любое сообщение администратора ретранслируется всем пользователям имеющим тот же язык локали, а сообщения пользователей передаются всем администраторам. Пока что обрабатываются только текстовые сообщения, но есть возможность ответа на сообщение (и над её реализацией пришлось поломать голову).
Любое сообщение в чат бота (message) ретранслируется нескольким получателям. Id этих сообщений фиксируются в таблице client_message и ответ будет осуществляться на эти сообщения. Получив такой ответ, надо найти id сообщения инициировавшего то сообщение, на которое выполнен ответ и отвечать уже на него. Сейчас всё выглядит довольно просто, но я потратил около получаса, чтобы отладить это.
Итак, я разработал бота, облегчающего жизнь пользователям 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-а и поделился с вами историей его создания. Разумеется, я не собираюсь останавливаться на достигнутом и буду расширять его функциональность, но это всё уже в следующем году, а пока…
Всех с наступающим Новым Годом!