БФЛ КонтентДокументация проекта
Разделы документации
Для разработчика

Карта функциональных этапов

Обновлено 16 сентября 2026БФЛ Контент
На этой странице
  1. Получить событие Telegram
  2. Разобрать команду или ответ
  3. Проверить участника, копию и версию
  4. Принять операцию атомарно
  5. Показать принятие запроса
  6. Определить окно поиска
  7. Найти кандидатов
  8. Проверить URL и загрузить статью
  9. Извлечь текст и дату
  10. Оценить и отобрать темы
  11. Сохранить подборку и отправить карточки
  12. Принять выбор новости
  13. Подготовить подтверждённые факты
  14. Составить текст и надписи обложки
  15. Собрать подпись и проверить длину
  16. Проверить смысл трёх полей
  17. Создать или переиспользовать иллюстрацию
  18. Импортировать PNG
  19. Собрать фирменную обложку
  20. Получить обложку и доставить превью
  21. Сохранить готовность к согласованию
  22. Запросить и применить доработку
  23. Опубликовать одобренную версию
  24. Отклонить черновик
  25. Продолжить сохранённую попытку
  26. Сохранить ошибку и сообщить о ней

Что делает каждый этап, какие данные ему нужны и почему он стоит здесь.

26 этапов — объяснение процесса, а не число узлов n8n или одна обязательная цепочка. Основной workflow содержит 262 узла; десять активных схем — 422. Проверки прав повторяются в разных ветках. Точные переходы доступны в реестре узлов.

01Получить событие Telegram

Единственный действующий Telegram Trigger передаёт событие в BFL-content. Данные о пользователе, чате и сообщении сохраняют связь с исходным действием.

Вход
Сообщение, reply или callback_query.
Выход
Событие для проверки и маршрутизации.
При сбое
Не создавать второй обработчик того же бота ради тестирования.
Почему здесь
Все действия должны проходить одинаковые проверки доступа и повторов.

Узлы экспорта: Telegram — участники проекта; Проверить участника Telegram

02Разобрать команду или ответ

Определить /start, /post, /news, /retry, /resume, кнопку либо ожидаемый reply. /start отделяет регистрацию от выдачи прав; /post и /news — самостоятельные ветки.

Вход
Текст сообщения, callback_data, reply_to_message.
Выход
Тип действия и его аргументы, ссылка на исходное сообщение.
При сбое
Неизвестный ввод вне ожидаемого reply может остаться без ответа. /help и /cancel не реализованы.
Почему здесь
Один и тот же текст в новой команде и в ответе на доработку означает разные операции.

Узлы экспорта: Распознать команду или решение; Маршрут Telegram-события; Ответить на безопасный /start

03Проверить участника, копию и версию

Сверить numeric ID, active, private chat=from.id и is_bot=false; для кнопки — собственную доставленную копию и последнюю версию. Перед внешними запросами допуск проверяется повторно.

Вход
Настоящий отправитель, личный чат, действующая роль и запись доставленной копии.
Выход
Разрешённое действие либо отказ.
При сбое
Чужой пользователь, неверное сообщение, устаревшая версия или закрытое состояние останавливают действие.
Почему здесь
Подписанный контекст и наличие кнопки не заменяют текущую роль и общий захват состояния.

Узлы экспорта: Проверить участника Telegram; Проверить владельца версию и статус; Проверить актуальность замечаний; Проверить новостную кнопку; Допуск → OpenAI — написать текст

04Принять операцию атомарно

Уникальные индексы и условные операции БД резервируют событие, версию и выбор статьи. Пустой результат захвата не разрешает внешний запрос.

Вход
Проверенное событие и сохранённое состояние.
Выход
Одна принятая операция с сохранённым владельцем блокировки.
При сбое
Повтор или уже выполняемая операция получает отказ/статус ожидания; неизвестный внешний исход не считается свободной попыткой.
Почему здесь
Чистая функция guard проверяет данные, но не заменяет атомарную операцию в БД.

Узлы экспорта: Зарезервировать версию; Принять статью атомарно; Принять поиск атомарно

05Показать принятие запроса

Отправить или обновить статус инициатору до долгой подготовки. Контекст связывает дочерний execution с участником. Callback отдельно получает всплывающее подтверждение.

Вход
Успешно принятая операция.
Выход
status_message_id и видимое «Взято в работу».
При сбое
Сбой статуса фиксируется отдельно и не повторяет генерацию.
Почему здесь
Инициатор видит принятие запроса. Измерение 2,208 с относится к восстановлению 349, а не гарантии будущих запросов.

Узлы экспорта: Подтвердить приём задания; Выбрать актуальный статус; Выбрать актуальный статус

06Определить окно поиска

Вычислить календарный диапазон по Europe/Moscow, включая сегодня. Допустимы 7 и 30 дней; тема поиска ограничена 200 единицами UTF-16.

Вход
/news с необязательными периодом и темой; доверенное серверное время.
Выход
from, to, days, timezone, topic.
При сбое
Неподдерживаемый период отклоняется; дата не назначается моделью.
Почему здесь
Свежесть материала должна определяться одинаково во всех стадиях.

Узлы экспорта: Подготовить подборку

07Найти кандидатов

Вызвать Responses с web_search, проверить и сохранить фактические URL. Текущий код допускает до 20 кандидатов из семи разрешённых издателей.

Вход
Период, тема, список разрешённых источников и поисковый prompt.
Выход
Ссылки кандидатов, поисковые источники, response_id.
При сбое
Незавершённый ответ, отказ, невалидный JSON или отсутствие реального поиска — отдельные ошибки.
Почему здесь
Модель не должна добавлять выдуманную ссылку лишь потому, что она выглядит правдоподобно.

Узлы экспорта: Подготовить web search; OpenAI — поиск по официальным сайтам; Проверить результат поиска

08Проверить URL и загрузить статью

Канонизировать URL, проверить точный хост и путь, загрузить HTML, проверяя каждый переход. Credentials OpenAI и Telegram в запрос к изданию не передаются.

Вход
Кандидат из фактического поиска.
Выход
HTTP-код, конечный адрес, тип данных, время и тело ответа.
При сбое
Недоступность, неподдерживаемый адрес, недопустимый редирект или формат отмечаются как причина пропуска.
Почему здесь
Поисковый фрагмент не равен прочитанной статье.

Узлы экспорта: Начать чтение; HTTP статья 0; Проверить HTTP 0

09Извлечь текст и дату

Выделить тело статьи и публикационную дату, сохранив контекст её происхождения. Удалить меню, стили и служебные блоки с сохранением границ слов.

Вход
Настоящий HTML-ответ.
Выход
read_id, article_text, published_date, publication_date_context, fetched_at.
При сбое
Неоднозначная дата или испорченное извлечение не заменяются догадкой модели.
Почему здесь
В прошлой версии склейки слов влияли на проверку цитат и отбор.

Узлы экспорта: Извлечь статью и дату; Проверить извлечённую статью

10Оценить и отобрать темы

Оценить прямую или смежную связь с долгами граждан. Удалить дубли и выбрать до пяти тем. Разнообразие источников — предпочтение; дословные цитаты не обязательны для показа идеи.

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

Узлы экспорта: OpenAI — классифицировать темы; Выбрать темы без дублей

11Сохранить подборку и отправить карточки

Сохранить карточки общей подборки и доставить их обоим активным участникам. Журнал хранит отдельное подтверждение каждой копии; вторая доставка не запускает новый поиск.

Вход
Отобранные темы.
Выход
Карточки с кнопкой «Подготовить пост», записи о доставке.
При сбое
Повтор события не должен переслать подборку заново. Частичная доставка требует учёта уже отправленных карточек.
Почему здесь
Выбор должен относиться к сохранённой статье и исходному сообщению.

Узлы экспорта: Подготовить записи карточек; Отправить карточку; Сохранить доставку карточки

12Принять выбор новости

Проверить active, личный чат, доставленное message_id, срок карточки и состояние ready/offered. Общий атомарный claim по статье допускает один материал для обоих участников.

Вход
Новостная кнопка в зарегистрированной копии участника и действующая карточка.
Выход
Новостной черновик, связанный с карточкой и URL.
При сбое
Устаревшая подборка или уже выбранная карточка не запускает вторую обработку.
Почему здесь
Выбор темы начинает подготовку, но не даёт разрешения отправлять её в канал.

Узлы экспорта: Проверить новостную кнопку; Принять статью атомарно; Подготовить пост по сохранённой новости

13Подготовить подтверждённые факты

BFLNewsSelected041 формирует и проверяет 1–6 фактов по выбранному источнику: каждый до 700 единиц, с 1–3 существующими source_ids и совпадающим document_key.

Вход
Текст выбранной статьи, document_key и блоки источника.
Выход
Проверенный fact_pack или конкретная причина отказа.
При сбое
Сохранённый источник не разрешает додумывать факты. Для неподтверждённого вывода о действующей норме возвращается selected_primary_document_required.
Почему здесь
Отдельное судебное дело, мнение эксперта и действующая правовая норма требуют разных формулировок.

Узлы экспорта: Подготовить факты; OpenAI — факты выбранной темы; Итог фактов

14Составить текст и надписи обложки

Получить тело поста, cover_title, cover_subtitle и описание сцены. Проверить завершённость ответа и соответствие полей контракту.

Вход
Тема пользователя либо выбранные факты, редакционные правила, доступный бюджет.
Выход
Структурированный кандидат для проверки.
При сбое
Невалидный JSON, пустое поле и ответ модели не должны маскироваться общим успехом.
Почему здесь
Подпись, надписи и иллюстрация должны описывать один материал.

Узлы экспорта: Собрать текстовый запрос 0; OpenAI — написать текст; Извлечь готовый текст

15Собрать подпись и проверить длину

Составить body и суффикс со ссылкой. Полная подпись ограничена 1024 единицами UTF-16. Предпочтение 700–900 не является минимумом. Допускаются две попытки сокращения с повторной смысловой проверкой новости.

Вход
Тело текста и сохранённый canonical URL.
Выход
Нормализованный caption в допустимом бюджете.
При сбое
Редакционные заметки не являются hard_errors. После смысловой правки длину считать повторно.
Почему здесь
Ссылка занимает часть места; допустимый текст до правки может перестать помещаться после неё.

Узлы экспорта: Текст соответствует лимитам?; Сократить текст 1; Сократить текст 2

16Проверить смысл трёх полей

Сопоставить body, cover_title и cover_subtitle с фактами: числа, субъектов, отрицания, условия и атрибуцию. Допустимы до десяти issues, один цикл точечных правок и повторная проверка. Длинная смысловая правка может вернуть поток к сокращению.

Вход
caption_body, cover_title, cover_subtitle, факты и фрагменты источника.
Выход
Подтверждённый комплект текста либо конкретные issues.
При сбое
Сбой API и отсутствие ответа не равны pass=true. Не исправлять подпись, забыв старый заголовок на обложке.
Почему здесь
Смысл проверяется до затрат на иллюстрацию и до согласования.

Узлы экспорта: OpenAI — сверить подпись; Точечно исправить смысл; OpenAI — повторная смысловая проверка

17Создать или переиспользовать иллюстрацию

Сохранить исходную иллюстрацию, если нет распознанной явной просьбы заменить её. Иначе после проверок текста выполнить один запрос Images: PNG 1536×1024, quality=medium.

Вход
Проверенный текст, image_prompt и наличие сохранённой сцены.
Выход
PNG или ссылка на подтверждённый сохранённый результат операции.
При сбое
Неизвестный исход платного вызова блокирует слепой повтор.
Почему здесь
Правка одного предложения не должна автоматически создавать новую платную картинку.

Узлы экспорта: Есть сохранённый исходник?; Зарезервировать генерацию изображения; OpenAI — создать иллюстрацию

18Импортировать PNG

Передать raw bytes в POST /v1/images. Renderer проверяет тип, размеры и структуру, вычисляет SHA-256 и сохраняет объект.

Вход
Бинарный PNG из генератора.
Выход
image_id, sha256, размеры, asset_path и reused.
При сбое
Невалидный или слишком большой PNG отклоняется; идентификатор не выдумывается.
Почему здесь
Исходная сцена должна пережить перезапуск и использоваться в следующих версиях.

Узлы экспорта: Восстановить бинарный PNG; Сохранить PNG в renderer; Проверить импорт PNG

19Собрать фирменную обложку

Renderer 0.3.0 собирает обложку 1080×1080 по шаблону ff-square-0.1: логотип, шрифты и графика накладываются на сохранённый исходник. Название каталога v0.2 не определяет версию сервиса.

Вход
draft_id/version, image_id, заголовок, подзаголовок и версия шаблона.
Выход
PNG 1080 × 1080 и метаданные шаблона/объекта.
При сбое
Другое содержимое с тем же номером версии возвращает конфликт; нужен новый номер, а не перезапись.
Почему здесь
Изменение надписей не требует поручать генератору воспроизводить фирменный стиль.

Узлы экспорта: Подготовить рендер версии; Создать обложку версии; Проверить контракт обложки

20Получить обложку и доставить превью

Получить PNG, сверить сохранённый хеш и доставить одно общее фото с полной подписью и тремя кнопками каждому активному участнику. У каждого получателя отдельный результат в журнале.

Вход
Сохранённый asset_id, caption и кнопки текущей версии.
Выход
Подтверждённые message_id копий и общий photo file_id; первичная успешная копия сохраняется также в прежних полях.
При сбое
Неизвестная доставка требует выяснения; сообщение «Готово» не должно опережать подтверждённую отправку.
Почему здесь
Каждая кнопка связана с версией и сообщением, которое действительно получил этот участник.

Узлы экспорта: Загрузить PNG для превью; Прислать пост одним фото; Прислать исправленный пост прежним фото

21Сохранить готовность к согласованию

Проверить подтверждённые доставки, сохранить file_id и каноническое approval_message_id. Все копии учтены в журнале; awaiting_approval разрешает одно общее решение.

Вход
Подтверждённая доставка превью.
Выход
Активный комплект, связанный со своими кнопками.
При сбое
Сбой записи после отправки — отдельная ситуация; нельзя автоматически отправлять фото ещё раз.
Почему здесь
Публикация и защита старых кнопок используют сохранённые идентификаторы.

Узлы экспорта: Проверить фото с подписью; Сохранить согласование фото

22Запросить и применить доработку

Закрепить ForceReply за actor/chat/prompt участника, начавшего правку. Ответ другого участника не подходит. Применить точную замену или обычную редакцию; новая версия доставляется обоим, старая перестаёт разрешать действия.

Вход
Кнопка «Доработать», затем reply с замечаниями.
Выход
Новая версия и новое превью.
При сбое
Неоднозначная замена, старый reply или потеря смысла останавливают изменение без удаления прежнего комплекта.
Почему здесь
Пользователь одобряет каждую изменённую версию отдельно.

Узлы экспорта: Запросить замечания ответом; Подготовить новую версию; Применить точную замену

23Опубликовать одобренную версию

После настоящего нажатия любого активного участника атомарно захватить общую публикацию, отправить сохранённые caption и file_id и записать подтверждение Telegram. Повторная генерация не требуется.

Вход
Разрешённое нажатие «Опубликовать» под текущим превью.
Выход
Одна подтверждённая публикация и уведомление обоим участникам; старые клавиатуры снимаются.
При сбое
Старая версия, повторный клик или неизвестный исход отправки не должны порождать новый пост.
Почему здесь
Одобрение не разрешает переписывать текст или подменять изображение после проверки.

Узлы экспорта: Заблокировать повторную публикацию; Опубликовать фото с полным текстом; Проверить публикацию одного фото; Завершить публикацию с подписью; Общее решение → Завершить публикацию с подписью

24Отклонить черновик

Записать status=rejected и сохранить историю. Это не удаление черновика и не отмена выполняющейся генерации. Claim статьи автоматически не освобождается.

Вход
Кнопка «Отклонить» под разрешённой версией.
Выход
Общее состояние rejected и уведомления участникам; старые кнопки недействительны.
При сбое
Повторное решение или старая версия не должны менять актуальный комплект.
Почему здесь
Отказ от подготовки не равен удалению уже опубликованного сообщения.

Узлы экспорта: Сохранить отклонение; Подтвердить отклонение

25Продолжить сохранённую попытку

/retry для подходящего text_failed создаёт новую версию с сохранённым контекстом. /resume продолжает импорт, рендер или превью той же последней версии со статусом image_upload_failed, render_failed или preview_failed. Служебный BFLEditorPostResume339 отдельно продолжил прежнюю v1 по серверному доказательству остановки до запроса. Его одноразовое разрешение уже использовано.

Вход
/retry DRAFT_ID V [замечания] либо /resume DRAFT_ID V.
Выход
Продолжение до превью либо конкретный отказ/блокер.
При сбое
Unknown outcome, опубликованная или заменённая версия не допускают повтор. /retry первоначального обычного поста без пригодного родителя не является универсальным восстановлением.
Почему здесь
Отдельная исправленная служебная ветка не должна расходиться с обычным маршрутом.

Узлы экспорта: Подготовить восстановленную правку; Проверить безопасное восстановление; Продолжить сохранённый текст; Продолжить существующий BFL-content

26Сохранить ошибку и сообщить о ней

Сохранить известный бизнес-отказ в версии. Необработанное исключение передать в Error Workflow: audit связывает execution с участником, сервис пытается уведомить инициатора и владельца. Генерация автоматически не повторяется.

Вход
Исходное исключение, стадия, состояние внешнего запроса и сохранённые результаты.
Выход
Диагностируемая остановка; команда продолжения только при допустимом состоянии.
При сбое
Неизвестный внешний исход блокирует повтор. Отказ Telegram/сервиса или аварийное завершение процесса не гарантируют доставку сообщения об ошибке.
Почему здесь
Для восстановления нужно знать не только ошибку, но и то, что уже было выполнено.

Узлы экспорта: Сохранить ошибку текста; Сохранить ошибку рендера; Сохранить неопределённую публикацию; Обновить статус ошибки; Ошибка выполнения; Сообщить о техническом отказе