Таблица в роли CMS: как управлять контентом чат-бота из обычной таблицы

Запустили внутренний HR-сервис для сотрудников в формате телеграм-бота. В этом сервисе публикуются новости, информация о работе в компании, отправляются пользователям уведомления и опросы. Контентом управляет HR-менеджер через табличный сервис Seatable. При этом код бота на Python не меняется – таблица Seatable работает как админ-панель сайта.


Задача: гибко управлять контентом бота без участия программиста

В HR-сервисе требовалось, чтобы управление контентом было максимально простым и не требовало бы участия программиста. В идеале так: IT-отдел разрабатывает решение, передает его HR-отделу, далее HR может самостоятельно редактировать контент.

В результате обсуждения с HR-отделом, сформировали список требований к сервису:

  • Обновление контента без деплоя и правок кода.
  • Возможность добавлять новые разделы меню любой глубины вложенности — тоже без разработчика.
  • Поддержка форматирования в постах: заголовки, списки, ссылки, картинки, PDF-вложения.
  • Возможность подготовить рассылку для сотрудников и запустить её из бота — сразу или отложенно.
  • Каждому пользователю присваивать роль – новичок и действующий сотрудник. У каждой роли должна быть своя ветка контента.
  • Новым сотрудникам присылать периодически опрос о работе в компании – через неделю, месяц, 3 и 6 месяцев, и через год работы в компании.
  • Сделать форму обратной связи, где пользователь может написать вопрос HR-менеджеру.


Решение: API Seatable + модель данных

Контент решили публиковать в сервисе Seatable. Seatable – это no-SQL-платформа, которая позволяет работать с базами данных. Интерфейс пользователя реализован в виде таблиц. В ячейки можно класть тексты, изображения, видео, PDF, URL, формулы, геометки и др. В одной ячейке можно сверстать текст на Markdown – выделить заголовок, сделать список, изменить шрифт, добавить изображение, то есть целиком сверстать пост для телеграма.

У Seatable есть API на основе REST. API имеет URL-адреса, принимает на них запросы и возвращает ответы в формате JSON. Использует стандартные коды ответов HTTP. По API можно полностью управлять таблицами: создавать, изменять и удалять все объекты – таблицы, строки, колонки.


Модель данных: как устроен контент в таблице

Я разработала и реализовала на Python модель данных, которая описывает, как контент раскладывается по таблицам и как бот его читает.

Дерево меню. Контент в боте организован по принципу дерева. Сначала пользователь попадает на главное меню. Оттуда может переходить на меню второго уровня, затем на меню третьего уровня и так далее. Глубина вложенности не ограничена.

Единая структура узла. Главное меню и все вложенные подменю имеют одинаковую структуру:

  • Сверху находится контентная часть — иллюстрация и текст. В таблице управления контентом эта часть задается в ячейке на пересечении колонки Content и строки Info.
  • Снизу находятся кнопки меню. В таблице управления контентом названия кнопок находятся в колонке Name под Info.

Каждое подменю — это отдельная таблица с такой же структурой. Переход в подменю — это по сути смена таблицы, из которой бот рендерит экран.


Пример. Иллюстрация, подпись к ней и кнопки пробрасываются из таблицы в бот.




Три типа кнопок в меню:

  • Подменю (Submenu_link) — вложенное меню. По клику на кнопку этого типа пользователь проваливается в меню уровнем ниже, которое организовано по такому же принципу — контент (иллюстрация и текст) и кнопки.
  • Ссылка (External_link) — внешняя ссылка на сайт, форму, документ и т.д. По клику на кнопку этого типа пользователь переходит по указанной внешней ссылке.
  • Контент (Button_content) — контент под кнопкой. По клику на кнопку этого типа пользователю отдается контент в виде поста — иллюстрация и текст. Дополнительно к этому посту можно приложить файл. Для этого нужно добавить файл в колонку с вложением (Attachment).

Тип кнопки определяется тем, какое поле в строке заполнено. Приоритет проверки — Submenu_link, затем External_link, затем Button_content. Это даёт HR-менеджеру гибкость: не нужно указывать тип явно, всё определяется по факту заполненных ячеек.


Пример. Кнопки, которые ведут в подменю, и кнопки, содержащие только контент:



Пример. Кнопки со ссылками:



На основе этой модели данных разработала платформу, где контент полностью редактируется через таблицы. Пользователь кликает кнопки в боте, программа обращается в Seatable по API, забирает контент и рендерит пользователю запрошенное меню с актуальным контентом. Посмотрите видео, как это работает:


Если видео не работает, смотрите на Rutube.


Архитектура сервиса

Схематично сервис устроен так:

[HR-менеджер] → [Seatable] ← [Python-бот (aiogram)] → [Telegram] → [Сотрудник]

HR-менеджер работает только с таблицами. Разработчик не участвует в этом потоке. Бот на Python читает таблицы через API и рендерит из них сообщения в Telegram.


Стек технологий

  • Python 3.12 — основной язык проекта.
  • aiogram 3.x — асинхронный фреймворк для телеграм-ботов.
  • Seatable — хранилище контента и данных в виде таблиц.
  • aiohttp — асинхронный HTTP-клиент для работы с REST API Seatable.
  • cachetools — кеширование авторизационных данных с TTL-политикой, чтобы разгрузить API таблицы на частых обращениях.
  • SQLite — хранение состояния FSM: навигация по меню, заполнение форм. FSM держится в оперативной памяти и записывается в SQLite при остановке программы.
  • asyncio — планировщики фоновых задач для рассылки пульс-опросов.
  • logging — стандартное логирование с ротацией файлов и маскированием пользовательских данных в сообщениях.
  • Docker — сборка и запуск.


Паттерны программирования

  • Конечный автомат (FSM). Для навигации по меню и заполнения форм используется собственный StateManager, который хранит состояние каждого пользователя в оперативной памяти и при необходимости записывает его в SQLite. Каждый пользователь — это словарь с текущим меню, историей навигации и данными формы. Такой подход позволяет пользователю продолжить сессию после перезапуска бота с того места, где остановился.
  • Стек навигации. История переходов между меню хранится как стек — при нажатии кнопки текущий узел кладётся в navigation_history, при возврате назад достаётся оттуда. Это даёт универсальную кнопку «Назад», которая работает на любой глубине дерева, независимо от структуры конкретного раздела.
  • Data-driven UI. Клавиатуры и содержимое сообщений не захардкожены в коде, а собираются из строк таблицы. Один и тот же обработчик рендерит меню любого уровня вложенности.
  • Приоритеты полей вместо явного типа. Тип кнопки (подменю / внешняя ссылка / контент) определяется по факту заполненных ячеек в порядке приоритета. HR-менеджеру не нужно указывать тип кнопки — достаточно заполнить нужное поле.
  • Middleware для сквозной логики. Проверка доступа, определение роли, логирование вынесены в отдельный слой от обработчиков.
  • Кеш с TTL. Часто запрашиваемые данные кешируются на час через TTLCache из cachetools. Это снимает нагрузку с API на частых обращениях.
  • Ретраи с экспоненциальной паузой. Создание задач на опросы в Seatable обёрнуто в цикл ретраев — если сеть моргнула, задача не потеряется.
  • Планировщики на asyncio. Фоновые задачи реализованы как отдельные корутины, запускаемые при старте бота через asyncio.create_task. При остановке бота задачи корректно отменяются, состояние сохраняется.
  • Маскирование пользовательских данных в логах. Данные пользователей проходят через утилиту mask_pii — в файл попадают первые три символа и звёздочки.

👉 Посмотреть код можно здесь.


Что удалось построить на этой архитектуре

  • Справочник — дерево с контентом: информация о работе столовой, о спортзале, парковке, корпоративных мероприятиях и т.д.
  • Формы обратной связи. Форма — это тоже таблица, просто с другой схемой строк: вопросы, варианты ответа, признак свободного ввода. Бот сам определяет, что перед ним — меню или форма, по наличию признаков в строке Info. Ответы пользователей записываются в отдельную таблицу, указанную в самой форме.
  • Рассылки. Список уведомлений — тоже таблица. HR готовит уведомление заранее (текст, картинка, вложение), а потом через команду /broadcast выбирает его из списка и отправляет всем сотрудникам сразу или создает отложенную отправку на конкретное время.
  • Пульс-опросы. Опросы отаправляются автоматически через 1 неделю, 1, 3, 6 месяцев и год после трудоустройства. Контент опросов — тоже таблица. Планировщик каждый день читает список задач и отправляет соответствующий опрос тем, у кого сегодня подходит дата.


Совместимость с другими платформами

Изначально проект разрабатывался под Seatable, но позднее мигрировал на другую аналогичную платформу — NocoDB. Функциональность осталась прежней, поменялось только API. Миграция обошлась переписыванием одного модуля-клиента к API, а вся бизнес-логика – дерево меню, формы, планировщики, рассылки) осталась нетронутой.

Из-за замедления телеграма в России, возможно будет удобно перейти на другой мессенджер. Для этого достаточно переписать модули, которые работают на aiogram — фреймворке для телеграм-ботов.


Итог

HR-отдел получил доступ к Searable-админке и управляет контентом сам. Обновления контента, подготовка уведомлений, запуск опросов — всё это делается через таблицу без обращения к разработчику. Разработчик подключается только тогда, когда нужна новая функциональность — например, новый тип поведения бота.

Next Post