Таблица в роли 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-админке и управляет контентом сам. Обновления контента, подготовка уведомлений, запуск опросов — всё это делается через таблицу без обращения к разработчику. Разработчик подключается только тогда, когда нужна новая функциональность — например, новый тип поведения бота.