Skip to content

ITS Reg - сервис для NoCode создания телеграм-ботов на основе конечных автоматов

Notifications You must be signed in to change notification settings

bmstu-itstech/itsreg

Repository files navigation

ITS Reg

ITS Reg - сервис для LowCode/NoCode создания телеграм-ботов на основе конечных автоматов.

Как запустить?

cp .env.example .env

Далее заполнить .env файл.

Для запуска полностью в docker-compose:

docker compose --env-file .env up -d

Для запуска вне докера:

DATABASE_URI='' PORT=8500 JWT_SECRET=s3cr3t go run cmd/http/main.go

где DATABASE_URI - строка подключения к Postgres базе данных; PORT - порт, который будет прослушиваться сервисом; JWT_SECRET - ключ шифрования JWT-токена.

Как пользоваться?

На данный момент сервис не имеет клиента. Создание и управление ботами на платформе осуществляется через HTTP-запросы. OpenAPI спецификация описана в [api/openapi/bots.yaml]. Запросы к серверу должны содержать JWT-токен:

GET localhost:8500/api/v2/bots
Authorization: Bearer <place-your-jwt-token here>

Архитектура платформы

Упрощённая схема взаимодействия компонентов бота:

 ---------------------------    ---------------------
|                           |  |                     |    ------------------------
| telegram.InstanceManager  |  | command.Process     --> | telegram.MessageSender |
|                           |  |                     |    ------------------------
|   -----------------       |  |  -----------------  |    -----------------------------
|  | Bot #1 instance | -    |  | | bots.Partcipant | --> | ports.ParticipantRepository |
|   -----------------    \  |  |  -----------------  |    -----------|-----------------
|                         \ |  |  -----------------  |    ---------- V --------
|   -----------------      \   | | bots.Bot        | |   | postgres.Repository |
|  | Bot #2 instance | ------ >| |  -----------    | |    ---------- ^ --------
|   -----------------      /   | | | bots.Node |   | |    -----------|-----------
|                         / |  | |  -----------    | --> | ports.Bots.Repository |
|   -----------------    /  |  | |  -----------    | |    -----------------------
|  | Bot #2 instance | -    |  | | | bots.Node |   | |
|   -----------------       |  | |  -----------    | |
|                           |  |  -----------------  |
 ---------------------------    ---------------------

Бот как конечный автомат

Бот (bots.Bot) есть реализация конечного автомата. Конечный автомат (далее - КА) bots.Script состоит из:

  • узлов (bots.Node);
  • точек входа (bots.Entry).

Узел - минимальная структурная единица бота. Каждый узел характеризуется уникальным номером в рамках бота - состояние (state). Состояние есть положительное целое число. Узел состоит из:

  • состояния (Node.state)
  • названия узла (Node.title);
  • массива сообщений (bots.Message), отправляемых пользователю;
  • упорядоченного списка исходящих рёбер (bots.Edge);
  • опций (кнопок) ответа пользователем (bots.Option).

Точка входа есть именнованное состояние, с которого начинается прохождение сценария пользователем. По умолчанию используется точка входа start - при использовании пользователя команды /start.

Рассмотрим на примере.

                              ------------
                     красная | Реальность |
 -----   *   -----------   /  ------------   -------------------
| Имя | --> | Таблетка? | +-----------------| Некорректный ввод |
 -----       -----------   \  -----    *     -------------------
                   ^   синяя | Сон |                  |
                   |          -----                   |
                   |                                  |
                    ---------------------------------

Необходимо получить от пользователя его имя и выбор таблетки: красной или синей. Очевидно, что каждому вводу будет соответствовать своё состояние бота; а следовательно, и узел. Обозначим их как state=0 и state=1. А также информационные сообщения в случае различных выборов пользователя в последнем вопросе: 3, 4, 5.

Ввод имени будет представлен узлом:

{
  "state": 1,
  "title": "Имя",
  "messages": [ { "text": "Введите Имя" } ]
}

Далее необходимо описать упорядоченное множество исходящих рёбер из узла. Так как при любом вводе пользователя будет совершён переход к новому состоянию, используется предикат type=always. Следующее состояние to=2. Операция, совершаемая при переходе по ребру - единократное сохранение ответа в БД, то есть operation=save. Опции в данном узле опущены, так как не подразумевается использование пользователем кнопок (кроме кнопок клавиатуры, естественно).

Получаем:

{
  "state": 1,
  "title": "Имя",
  "messages": [ { "text": "Введите Имя" } ],
  "edges": [
    {
      "predicate": { "type": "always" },
      "to": 2,
      "operation": "save"
    }
  ]
}

Следующий узел имеет несколько возможных вариантов ответа. Используется связка predicate.type=exact и options.

{
  "edges": [
    {
      "predicate": {
        "type": "exact",
        "text": "Красная"
      },
      "to": 4,
      "operation": "save"
    },
    {
      "predicate": {
        "type": "exact",
        "text": "Синяя"
      },
      "to": 5,
      "operation": "save"
    }
  ],
  "options": [
    "Красная",
    "Синяя"
  ]
}

Пользователь при наличии кнопок всё ещё может ввести свой текст в ответ. Данная ситуация иногда требует собственной обработки с использованием предиката always с наименьшим приоритетом:

{
  "edges": [
    {
      "predicate": {
        "type": "exact",
        "text": "Красная"
      },
      "to": 4,
      "operation": "save"
    },
    {
      "predicate": {
        "type": "exact",
        "text": "Синяя"
      },
      "to": 5,
      "operation": "save"
    },
    {
      "predicate": { "type": "always" },
      "to": 3,
      "operation": "noop"
    } 
  ]
}

Стоит обратить внимание на operation=noop. В данном случае невалидный ввод не имеет смысла сохранять в БД, поэтому используется операция-заглушка: noop. Существуют ситуации, когда ввод, отличающийся от предложенных опций (кнопок) стоит сохранять. Например, когда пользователь должен выбрать из N вариантов или ввести свой вариант ответа. В последнем случае будет использована operation=save.

Другие, нерассмотренные в примере особенности:

  • состояние 0 зарезервировано как конец выполнения КА и не может являться состоянием бота.
  • за один узел может быть отправлено несколько сообщений подряд.
  • operation=save и operation=append имеют различную семантику при прохождении данного узла несколько раз: save перезаписывает существующий ответ, а append добавляет в конец через сепаратор \n. append может использоваться для вопросов с множественным выбором ответов.

Экспорт ответов

Запрос:

GET http://{{server}}/api/v2/bots/{{id}}/answers

возвращает CSV-таблицу ответов в следующем виде:

# Отметка времени Узел #1 ... Узел #N
abcdefg 2025-12-31 23:59 Ответ 1 ... Ответ N
... ... ... ... ...
abcd345 2025-12-31 23:59 Ответ 1 ... ...

Первый столбец обозначает thread_id - уникальный идентификатор каждого прохождения пользователем скрипта бота, начиная от любого entry. Проще говоря: каждая последовательность ответов от пользователя, начиная от команды /start.

Отметка времени есть начало прохождения скрипта начиная от точки входа.

Далее перечисляются узлы и ответы на них в последовательности увеличения state. Будут перечислены только те узлы, в которых существует хотя бы один ответ. Название столбца совпадает с Node.title.

Сервис допускает использование совместно с электронными онлайн-таблицами. Для этого необходимо в свободный лист таблицы вписать формулу:

=IMPORTDATA("http://{{server}}/api/v2/bots/{{id}}/answers?jwtToken={{place-jwt-token-here}}")

Через несколько секунд будет автоматически скачана и вставлена нередактируемая автоматически обновляемая таблица.

Чтобы насильно обновить данные в такой таблице, иногда требуется вставить формулу заново. Или написать расширение для электронных таблиц, чтобы сделать кнопку, которая делает эту рутину за Вас :).

Контакты

About

ITS Reg - сервис для NoCode создания телеграм-ботов на основе конечных автоматов

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages