Перейти к основному содержимому

🧩 Типы узлов (блоков)

Пул PostgreSQL сгенерированного бота закрывает свободные соединения после 60 секунд непрерывного простоя. При следующем запросе соединение создаётся снова; данные остаются в БД, постоянные фоновые задачи продолжают работу. Администратор может задать BOT_DB_POOL_IDLE_SECONDS: конечное положительное число секунд до 86400 включительно; неверное значение возвращает стандартные 60 секунд. Настройка относится к окружению запуска, не добавляет полей узлов и не ограничивает длительность SQL-запроса. Для применения к существующим ботам нужны повторная генерация и перезапуск. Освобождение соединения не гарантирует немедленного снижения RSS процесса Python.

В режиме роли bot_runtime функция настройки PostgreSQL хранит только идентификаторы проекта и токена, без ссылок на весь сценарий. При создании и каждом получении соединения из пула сохраняется установка app.project_id и app.token_id, обеспечивающая прежнюю область доступа к данным. Закрытый пул не удерживает сценарий через эту функцию. Это внутреннее исправление без новых настроек узлов и без гарантии немедленного снижения RSS; существующие сборки требуют повторной генерации и перезапуска.

При работе polling в общем Python-воркере регистрацией SIGTERM/SIGINT управляет воркер: отдельный Dispatcher не оставляет в цикле событий ссылку на сценарий через обработчик сигнала. Самостоятельный запуск Python без воркера сохраняет обычную регистрацию сигналов aiogram. Новых настроек узлов нет; нужны обновлённый runtime, повторная генерация и перезапуск ботов. Это устраняет конкретную удерживающую ссылку, а не гарантирует постоянный RSS процесса.

При остановке бот независимо пытается закрыть PostgreSQL, Redis, HTTP, аналитику и обработчики завершения с общим бюджетом ожидания 10 секунд. Ошибка или отмена одного этапа не пропускает остальные попытки. Ошибки и зависшие задачи отражаются в журнале; для PostgreSQL есть принудительное завершение пула. Успешное закрытие любого пользовательского ресурса и немедленное снижение RSS не гарантируются. Исходная отмена передаётся вызывающему коду. Поведение постоянного режима и настройки узлов сохраняются.

Служебные фильтры и обёртки логирования освобождают ссылки на выгруженные сценарии. Это внутреннее изменение: исходящие сообщения, история, аналитика и маскирование секретов сохраняют прежнее поведение; дополнительных настроек узлов и переключений режимов нет. Для применения изменений шаблонов к уже созданному боту нужны повторная генерация и перезапуск.

Неудачная инициализация БД или Redis освобождает созданные подключения без удаления состояния диалога в Redis. Временные оригиналы медиа удаляются только из управляемого каталога бота после завершения активных операций; пользовательские файлы сохраняются. Для существующих сборок нужны повторная генерация и перезапуск.

Флаг inline-кнопки hideAfterClick обрабатывается до перехода в любой целевой блок: клавиатура снимается, сообщение остаётся, сценарий продолжается. Обработка не зависит от БД; ошибка Telegram не прерывает переход. edit_message в режиме сохранения клавиатуры не возвращает снятое меню. Для применения исправления нужны повторная генерация и перезапуск бота.

Сбор пользовательского ответа в локальную переменную работает без БД пользователей и логирования сообщений. Функция времени ответа доступна независимо от этих настроек. Исправление ошибки get_moscow_time is not defined применяется после повторной генерации и перезапуска бота.

Запускаемые боты используют локальный Telegram Bot API при заданной переменной TELEGRAM_BOT_API_URL; без неё используется официальный API. Дополнительные токены в узлах сообщений используют тот же сервер. Адрес должен быть доступен из среды исполнителя; изменение требует повторной генерации и перезапуска бота.

Локальный сервер в режиме --local возвращает абсолютные пути файлов. Сохранение входящих фотографий и другие операции скачивания таких файлов требуют общей файловой системы и дополнительной поддержки; доступность самого API этого не гарантирует.

Полный справочник всех доступных узлов в конструкторе Telegram Bot Builder.

Карточка пользователя Bot Manager получает аватар через message.imageUrl = "{user_detail.photoDownloadUrl}". Фотография другого бота загружается в память с закрытого API с токеном менеджера, без файла на диске и без сохранения base64 в данных пользователя. Настройте API_BASE_URL (адрес сервера без пути) и STUDIO_BOT_MANAGER_TOKEN. Разрешён только адрес карточки пользователя; перенаправления запрещены, размер ограничен 5 МиБ, время — 10 секундами. При ошибке показывается текст карточки с её кнопками. photoUrl предназначен для интерфейса, а photoFileId можно повторно отправлять только исходным ботом. Для существующего сценария замените user_detail.photoUrl на user_detail.photoDownloadUrl в условии и изображении карточки, затем перегенерируйте и перезапустите бота.

В режиме polling при доступном Redis блокировка запуска принадлежит конкретному процессу: её продление и освобождение выполняются только владельцем. При потере блокировки или невозможности подтвердить продление в течение 50 секунд бот останавливается до окончания её 60-секундного срока. Изменение применяется после повторной генерации и перезапуска бота.

При включённой истории сообщений исходящая reply- или inline-клавиатура сохраняется вместе с сообщением. Повторное логирование одной отправки дополняет недостающие данные клавиатуры и ID блока без второго сообщения и без повторного увеличения аналитики. Для применения исправления к работающему боту нужна повторная генерация и перезапуск; уже утраченные клавиатуры старых сообщений автоматически не восстанавливаются.


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

Отмена запуска во время инициализации Redis или PostgreSQL освобождает уже созданное подключение или пул и сохраняет отмену запуска. Она не запускает повтор подключения и не переключает отменённый сценарий на локальное хранилище. Для существующих ботов требуется повторная генерация и перезапуск.

📨 Триггеры​

В общем Python-воркере устранено удержание последних 128 событий Update глобальным кешем aiogram 3.31.0. Определение типа события сохраняется, а выгруженные сценарии больше не удерживаются этим кешем. Для этого исправления нужен новый процесс runtime; оно не требует новых полей нод и не гарантирует немедленного возврата RSS к исходной величине. Настройки DB-пулов и новая очистка генерируемого кода отдельно требуют повторной генерации ботов.

Триггеры — это точки входа в сценарий. Они определяют, когда бот начинает выполнять цепочку действий.

🔔 Триггер команды (command_trigger)​

Срабатывает когда пользователь вводит команду (/start, /help, любая своя).

Для /start link_… одноразовый код скрывается в журнале, истории сообщений и аналитике; сам сценарий получает исходное сообщение для проверки кода.

НастройкаОписание
КомандаТекст команды: только латиница (/start, /buy). Кириллица недопустима
ОписаниеОтображается в меню команд Telegram (можно по-русски)
Аргументы → переменнаяsaveCommandArgsTo: для /donate 777 сохранит 777
Показать в менюДобавить команду в список меню бота
АвтопереходУзел, на который перейти после срабатывания

💬 Триггер текста (text_trigger)​

Срабатывает когда пользователь отправляет определённое текстовое сообщение.

НастройкаОписание
Тип совпаденияexact — точное совпадение, contains — содержит подстроку
СинонимыСписок слов/фраз для срабатывания
АвтопереходУзел для перехода

📩 Триггер входящего сообщения (incoming_message_trigger)​

Срабатывает на каждое входящее сообщение от пользователя (любой тип контента). Работает через middleware aiogram 3 — перехватывает сообщение до стандартных обработчиков и передаёт управление целевому узлу.

НастройкаПолеОписание
Тип чатаimtChatTypeFilterany — любой чат, private — только личные, group — группы и супергруппы
ID группыimtGroupChatIdФильтр по конкретной группе (без префикса -100). Активен при imtChatTypeFilter: "group"
Источник ID группыimtGroupChatIdSourcemanual — значение из imtGroupChatId, variable — из переменной groupChatVariableName
Остановка цепочкиimtStopOnFlagПо умолчанию true. Если у пользователя установлен флаг _stop_processing (узел stop_processing), middleware не вызывает следующий handler и сбрасывает флаг
АвтопереходautoTransitionToУзел, на который перейти после срабатывания

💡 Паттерн «охрана группы». Связка incoming_message_trigger (фильтр group + конкретный imtGroupChatId) → stop_processing → дальнейшая логика позволяет перехватывать сообщения в группе и блокировать стандартную обработку команд/текста для этого пользователя в текущем апдейте.

⚙️ Catch-all обработчики и предохранитель. Настройка «Catch-all обработчики» (тумблер в карточке бота, env CATCH_ALL_HANDLERS=0|1, по умолчанию включена) управляет генерацией универсальных обработчиков handle_unhandled_message, handle_unhandled_photo, fallback_callback_handler. Их можно отключить для лёгких ботов, чтобы убрать лишний код. Однако при наличии incoming_message_trigger, incoming_callback_trigger или динамических кнопок эти обработчики генерируются принудительно независимо от тумблера — без подходящего хендлера middleware этих триггеров в aiogram 3 не запускается. Это предохранитель-автодетект: формула generateCatchAll = (флаг ≠ 0) || есть incoming-триггеры/динамические кнопки.

🔒 Защита контента. Настройка «Защита контента» (тумблер в карточке бота, env PROTECT_CONTENT=0|1, по умолчанию выключена) управляет генерацией обёртки, которая добавляет protect_content=True ко всем исходящим сообщениям бота (запрет копирования/пересылки). Код защиты генерируется только при включённом флаге — при выключенной защите этот блок в сгенерированный код не попадает вовсе.

🔄 Живое обновление контента. Настройка «Живое обновление контента» (тумблер в карточке бота, env CONTENT_CACHE=0|1, по умолчанию выключена) управляет генерацией машинерии live-reload контента из таблицы _content: функций load_content/reload_content, фоновой перезагрузки кэша каждые 60 сек и мгновенного обновления через Redis pub/sub. При выключении (CONTENT_CACHE=0) эта машинерия не генерируется, но аксессор get_content(...) и кэш _content_cache остаются всегда — тексты нод берутся из вшитых в код fallback-значений, а живое обновление из БД отключается. Включение повышает расход памяти.


Триггер исходящего сообщения (outgoing_message_trigger)​

Срабатывает, когда бот отправляет сообщение в личку, группу, супергруппу или канал. Обёртки отправки ставятся при включённой базе пользователей.

В цепочку уходит id именно этой отправки и тип чата, поэтому условие is_group отличает группу от лички. Переменные {last_bot_message_id} и {message_text} пишутся только живому пользователю: id группы в них не попадает. Если человека в группе определить нельзя, переменные не обновляются, а переход по триггеру всё равно выполняется.

Медиагруппа представлена id первого сообщения. delete_message сам триггер не вызывает.


�🔘 Триггер inline-кнопки (incoming_callback_trigger)​

Срабатывает при нажатии inline-кнопки с определённым callback_data.

НастройкаОписание
Паттерн callback_dataСтрока для фильтрации (например work_)
Тип совпаденияstartsWith / equals / contains
Удалить префиксУбрать часть строки перед сохранением
Сохранить в переменнуюИмя переменной для значения callback_data

🔔 Триггер callback (callback_trigger)​

Срабатывает на конкретный callback_data от inline-кнопки.

НастройкаОписание
callback_dataПаттерн для перехвата
Тип совпаденияexact — точное, startswith — начинается с
Шаблон парсингаИзвлечение переменных из callback_data
АвтопереходУзел для перехода после срабатывания

Паттерн: кросс-пользовательский сценарий (модерация, заявки)​

Когда один пользователь (админ) действует в контексте другого (заявитель):

keyboard (кнопка с customCallbackData: "approve_{user_id}")
→ callback_trigger (startsWith "approve_", извлекает ID в _cb_dynamic_id)
→ bot_table READ (загружает данные по {_cb_dynamic_id})
→ edit_message / message (использует {app.*} из таблицы)

Это рекомендуемый способ реализации кросс-пользовательских сценариев. Не нужно менять движок — используй существующие ноды.

Кнопка с customCallbackData вида profile:name:{user_id} на input (или другую ноду без собственного startswith) ловится автоматически: виртуальный триггер слушает префикс profile:name:.

Для перехода к message кастомное значение также обрабатывает отдельный виртуальный триггер. Обработчик самой message продолжает слушать ID ноды. Если для того же значения есть явный callback_trigger, он имеет приоритет; второй обработчик не создаётся. Динамический customCallbackData с {переменной} использует виртуальный триггер для message, start и command. Для edit_message префикс обрабатывает собственный обработчик ноды.


👥 Триггер сообщений в группе (group_message_trigger)​

Срабатывает на сообщения в групповых чатах.

Пока BOT_RUNTIME_ENABLED выключен, поиск пользователя по теме фильтрует bot_users только по project_id. Когда флаг включён и задан BOT_DATABASE_URL, в запрос добавляется token_id.


👤 Триггер участника (member_trigger)​

Срабатывает на сервисные сообщения Telegram о входе или выходе участника из группы/супергруппы (new_chat_members, left_chat_member).

НастройкаПолеОписание
Тип событияmemberEventTypejoin — вход, leave — выход, both — оба события (два обработчика)
ID группыgroupChatIdОпциональный фильтр по чату (без префикса -100)
Источник ID группыgroupChatIdSourcemanual — из groupChatId, variable — из groupChatVariableName
Переменные входаsaveJoinedUserIdTo, saveJoinedUsernameToID и username вошедшего участника (по умолчанию joined_user_id, joined_username)
Переменные выходаsaveLeftUserIdTo, saveLeftUsernameToID и username вышедшего участника (по умолчанию left_user_id, left_username)
АвтопереходautoTransitionToУзел для перехода после срабатывания

Ограничения:

  • Бот должен быть добавлен в группу и видеть сервисные сообщения
  • При memberEventType: "both" генерируются отдельные обработчики для join и leave
  • Контекст сообщения (chat_id, message_*) заполняется через capture_message_context

⏰ Триггер по расписанию (schedule_trigger)​

Запускает цепочку автоматически по расписанию.

РежимОписание
ИнтервалКаждые N минут
ЕжедневноВ указанное время каждый день
ЕженедельноПо выбранным дням недели в указанное время
CronПроизвольное cron-выражение

Дополнительные настройки: часовой пояс, запуск при старте бота, лимит параллельных выполнений.

Если в сохранённом триггере отсутствуют правила расписания, генератор использует интервал пять минут.


🔌 API-триггер (api_trigger)​

Принимает HTTP-запрос от внешней системы (платёжка, CRM, backend) и запускает цепочку.

НастройкаОписание
МетодGET, POST, PUT, PATCH, DELETE
Путь/payment, /webhook/order и т.д.
SecretОбязателен; заголовок X-Api-Secret или Authorization: Bearer
Сохранить body/query/headersИмена переменных для данных запроса
Публичный URL{API_BASE_URL}/api/hooks/{projectId}{apiPath}

Ограничения MVP: max body 1 MB; rate limit на Node; бот офлайн → 503 bot_offline.


📤 Ответ API (api_response)​

Завершает HTTP-запрос, инициированный api_trigger.

НастройкаОписание
HTTP статусКод ответа (200, 400, …)
Content-Typeapplication/json, text/plain, text/html
ТелоJSON/текст с {переменными}

Без api_response в цепочке клиент получит 200 {"ok":true}; таймаут 30 с → 504.


💬 Сообщения и контент​

📝 Текстовое сообщение (message)​

Отправляет текстовое сообщение пользователю. Основной узел для коммуникации.

НастройкаОписание
ТекстТекст сообщения (поддерживает {переменные})
ФорматированиеБез формата / HTML / Markdown
КлавиатураБез кнопок / Inline / Reply
Сбор вводаОжидать ответ и сохранить в переменную
Типы вводаТекст, фото, видео, аудио, документ
Условные сообщенияРазный текст в зависимости от условий
Только для админовОграничить доступ

Одно вложение attachedMedia: ["{имя_переменной}"] может ссылаться на файловый объект из convert_file: бот декодирует его data из base64 и отправляет документ с именем fileName (по умолчанию export.csv). При ошибке отправки он записывает ошибку и отправляет текст сообщения. Эта обработка применяется, когда другие поля медиа у сообщения не заданы.

Форматирование текста:

  • none — обычный текст
  • html — поддержка тегов <b>, <i>, <u>, <s>, <code>, <pre>, <a href="">, <tg-spoiler>
  • markdown — *жирный*, _курсив_, `код`

ℹ️ Форматирование в режиме hot-reload (projectId / contentCache). Когда бот сгенерирован с горячей перезагрузкой контента, parse_mode вычисляется в рантайме в переменную _parse_mode. Статичный formatMode ноды имеет приоритет: markdown → Markdown, html → HTML. При formatMode: "none" (или отсутствии поля) работает автодетект — HTML подставляется только если в тексте есть HTML-теги (<b>, <i>, <u>, <s>, <code>, <pre>, <a , <tg-spoiler>), иначе форматирование отключено. Ограничение: режим formatMode статичен (известен на генерации) и не перечитывается из таблицы контента — меняется только при перегенерации бота; динамически из текста определяется лишь HTML-fallback при none.


📸 Медиа-узлы​

УзелЧто отправляетКлючевое поле
photoФотографиюimageUrl
videoВидеоvideoUrl
audioАудиофайлaudioUrl
documentДокументdocumentUrl, documentName
stickerСтикерstickerFileId или stickerUrl
locationГеолокацию—
contactКонтакт—

GIF — это не отдельный блок, а вид блока «Медиафайл» (type: media, поле attachedMedia). Файл .gif всегда уходит как анимация (send_animation, в группе InputMediaAnimation). Видео (mp4, webm, mov) можно отправить как гиф галочкой на этот файл: его URL попадает в sendAsGifUrls. Для новых сценариев тип animation не используй. Старый узел animation при открытии проекта становится media: animationUrl переносится в attachedMedia, автопереход сохраняется, mediaCaption если была — только в вызов анимации.

sticker остаётся отдельным шагом сценария без подписи (sendSticker её не принимает). Бот отправляет стикер, когда выполнение дошло до блока (кнопка или автопереход). Это не команда и не ответ на входящий стикер. Остальные медиа-узлы из таблицы выше — устаревшие одиночные типы; новый файл клади в media.


💾 Сохранить ответ (save_answer)​

Сохраняет ответ пользователя (текст, фото, видео и т.д.) в переменную для дальнейшего использования в сценарии.

НастройкаОписание
ПеременнаяИмя переменной для сохранения
Типы вводаКакой контент принимать (текст, фото, видео, аудио, документ)

⌨️ Клавиатура (keyboard)​

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

При нажатии кнопки с переходом на keyboard-ноду — кнопки текущего сообщения обновляются через editMessageReplyMarkup без отправки нового сообщения.

В цепочке «сообщение → главное меню → кнопка → подменю» сообщение сначала показывает главное меню. Подменю открывается только после нажатия кнопки. Генератор не переносит вложенную клавиатуру через другую keyboard-ноду или переход по кнопке на исходное сообщение. Для применения исправления к уже сгенерированному боту нужны повторная генерация и перезапуск.

У каждой inline-кнопки можно включить «Скрыть после использования» (data.buttons[i].hideAfterClick, boolean, по умолчанию false). Это поле кнопки, а не самой ноды. После нажатия снимается вся клавиатура исходного сообщения, текст остаётся, затем запускается целевой блок независимо от его типа. Например: «2 часа» → установка period = "2 часа" → подтверждение «Вы выбрали: {period}». Отдельное действие удаления не требуется. Подробности и JSON-пример: Keyboard-нода.

🔧 Авто-нормализация при записи через API/MCP. Если у message-ноды заданы инлайн/reply-кнопки прямо внутри неё (непустой buttons и keyboardType = inline/reply), при сохранении через API или MCP-инструменты они автоматически выносятся в отдельную keyboard-ноду (каноничная модель). В самой message при этом проставляется keyboardType: "none", buttons: [] и keyboardNodeId со ссылкой на созданную keyboard-ноду; рядом появляется keyboard-нода со смещением +360 по X. Операция идемпотентна — повторная запись не создаёт дублей. Валидация выдаёт неблокирующее предупреждение inline_keyboard_will_hoist для таких message-нод.


✏️ Редактировать сообщение (edit_message)​

Редактирует текст или кнопки уже отправленного сообщения.

НастройкаОписание
editKeyboardMode: 'node'Берёт клавиатуру из привязанного keyboard-узла
keyboardLayoutЕсли задан в keyboard-узле и autoLayout: false — кнопки группируются по рядам согласно layout

Динамический callback (customCallbackData)​

Кнопки, ведущие к edit_message, могут содержать customCallbackData с переменными (например "approve_{user_id}"). При нажатии динамическая часть извлекается в переменную {_cb_dynamic_id}, доступную в тексте редактирования и последующих узлах.


↗️ Переслать сообщение (forward_message)​

Пересылает сообщение в другой чат.

В режиме BOT_RUNTIME_ENABLED и заданного BOT_DATABASE_URL поиск исходного сообщения в bot_messages фильтрует и project_id, и token_id. Без флага условие прежнее.


⌨️ Кнопки​

Кнопки добавляются к узлу сообщения. Тип клавиатуры:

  • Inline — кнопки под сообщением
  • Reply — кнопки под полем ввода

Действия кнопок​

ДействиеЧто происходит
Перейти на узелПереход к другому блоку сценария
Открыть URLОткрывает ссылку в браузере
Mini AppОткрывает Telegram Mini App
Скопировать текстКопирует текст в буфер обмена
Запросить контактЗапрашивает номер телефона
Запросить геолокациюЗапрашивает местоположение
Выбор (selection)При multi-select — галочка ✅; с полем «Группа (радио)» (selectionGroup) — один выбор в группе (🔘/⚪️)
Готово (complete)Завершает multi-select и переходит дальше
Создать ботаЗапрос на создание управляемого бота

Дополнительно: перемешивание кнопок (shuffleButtons) для квизов и капч; allowMultipleSelection на узле — мультивыбор; selectionGroup у selection-кнопок — радиогруппа (один активный пункт в группе). Символы отметки настраиваются на ноде: checkmarkSymbol, radioSelectedSymbol, radioUnselectedSymbol (дефолты ✅ / 🔘 / ⚪️).


📋 Создать тему форума (create_forum_topic)​

Создаёт новый топик в форум-группе Telegram.


🔌 Интеграции и логика​

🌐 HTTP-запрос (http_request)​

Отправляет запрос к любому внешнему API.

НастройкаОписание
URLАдрес API (поддерживает {переменные} и ключи env бота)
МетодGET, POST, PUT, DELETE, PATCH
ЗаголовкиJSON с заголовками запроса ({имя} = сценарий + env)
ТелоJSON тело запроса
Authnone / bearer / basic / header / query — поля токена поддерживают {VAR} из env бота
ТаймаутВремя ожидания ответа (сек)
Сохранить ответИмя переменной для результата
Формат ответаJSON (по умолчанию) или файл (base64)

{имя} подставляется из переменных сценария и env бота (вкладка Бот → env у токена). При конфликте имён побеждают переменные сценария/FSM. Пример Bearer: httpRequestAuthBearerToken: "{API_TOKEN}".

Переменные сервера (SESSION_SECRET, ADMIN_API_KEY и др.) в {имя} не подставляются: бот видит только переменные, перечисленные администратором в WORKER_ENV_PASSTHROUGH (вне denylist, см. docs/deployment/WORKER_DOCKER.md).

🗄️ Таблица данных (bot_table)​

Работа с внутренними таблицами проекта — без SQL, через визуальный интерфейс.

Подстановки Bot Tables используют общий кеш на 60 секунд, включая пустой результат. Параллельные обращения ожидают одно обновление; строки и колонки загружаются отдельно, без размножения JSON-данных. Формат значений и доступные подстановки не меняются. При ошибке чтения возвращается пустой кеш, следующая попытка — через 60 секунд. Для применения оптимизации к существующему боту нужны повторная генерация и перезапуск.

Операции​

ОперацияОписание
ЧтениеПолучить строку(и) по условию
ВставкаДобавить новую строку (таблица создаётся автоматически)
ОбновлениеИзменить поля (атомарный increment/decrement)
UpsertВставить или обновить если существует
УдалениеУдалить строку(и) по условию
ПодсчётКоличество строк
Сумма / Макс / Мин / СреднееАгрегация по числовой колонке
УникальныеСписок уникальных значений колонки
Удалить всёОчистить таблицу

Операторы условий: равно, не равно, больше, меньше, содержит, пусто, не пусто.

Операции обновления: установить, увеличить, уменьшить, минимум, максимум — все атомарные.

Формат результата: первая строка (объект), все строки (массив), одно значение, количество, случайная строка.

Поддерживает сортировку, лимит, смещение (пагинация) и динамические имена таблиц через переменные.


🐘 PostgreSQL запрос (psql_query)​

Выполняет произвольный SQL-запрос к базе данных.

НастройкаОписание
SQL-запросЛюбой SQL. {имя} — параметр запроса, не вклейка текста
Сохранить результатИмя переменной
Формат результатаfirst_row — первая строка, json — список строк, text — текст по шаблону, affected — результат команды изменения данных
Источник подключенияПеременная бота (по умолчанию) / Строка подключения вручную / Встроенная БД (только с разрешения администратора)

Подключение к своей базе. Добавьте во вкладке «Бот» переменную, например MY_DB_URL, со строкой postgresql://user:pass@host:5432/db и выберите её в узле. Также можно ввести строку подключения вручную.

Ограничения:

  • Встроенная БД платформы (builtin) по умолчанию выключена: иначе любой бот мог бы читать чужие токены и данные. Администратор сервера может включить её переменной PSQL_BUILTIN_ENABLED=true, но только на однопользовательской установке. Пока флаг выключен, пункт DATABASE_URL скрыт из списка, у старых узлов с builtin показывается предупреждение, а сам узел не выполняет запрос и пишет ошибку в лог бота. После смены флага бота нужно перезапустить.
  • Серверный флаг PSQL_PANEL_DSN_DENIED по умолчанию выключен: узел в режиме переменной бота работает как раньше, в том числе если переменная называется DATABASE_URL. Если администратор задаёт PSQL_PANEL_DSN_DENIED=true и пересобирает бота, такое имя (без учёта регистра) не подключается: запрос не выполняется, в лог пишется та же ошибка, что и для выключенной встроенной БД. Своя переменная (MY_DB, BOT_DATABASE_URL) и строка подключения вручную не затрагиваются. В словарь бота DATABASE_URL этот флаг не убирает.
  • В строке подключения обязателен явный хост. Пустая строка, адрес без хоста, другая схема (не postgres/postgresql) и параметры host/hostaddr/passfile/service в query отклоняются. Если пароль не указан, он не подставляется из окружения сервера (PGPASSWORD, ~/.pgpass).
  • Каждый запрос обрывается через 15 секунд. Отдельной настройки таймаута у узла нет.

Переменные в запросе. {name} и {status} в тексте SQL — параметры. Значение не вклеивается в команду, поэтому кавычка во вводе пользователя не меняет запрос. Одно и то же имя можно писать несколько раз. Если переменной нет, в параметр уходит пустое значение SQL (NULL).

Пишите так:

SELECT * FROM orders WHERE name = {name} AND status = {status}
WHERE name ILIKE '%' || {q} || '%'
WHERE id = ANY({ids})

{ids} должен быть массивом. Для целой колонки, если переменная может оказаться текстом: WHERE telegram_id = {user_id}::bigint.

Так писать нельзя — смысл запроса ломается:

  • LIKE '%{q}%' и ' {name} ' — скобки становятся параметром внутри строки, поиск по подстроке не работает.
  • FROM {таблица} и имя столбца из переменной. Имя таблицы пишется буквами прямо в тексте запроса, иначе выполнение падает.
  • Блоки {#each} и формулы {=...} в SQL не раскрываются.

Старая запись WHERE name = '{name}' (кавычки вплотную к скобкам) по-прежнему передаёт значение параметром, кавычки не обязательны. Двойные кавычки вокруг скобок не снимаются.

Шаблон формата «текст» (textTemplate) — это текст сообщения, не SQL: {name} там подставляется в строку результата.

Когда использовать: сложные JOIN, аналитика, кросс-таблицы. Для простого CRUD лучше bot_table.


Преобразование файлов между форматами (например PDF → изображение, аудио → другой формат).


Ветвление логики: «если... то... иначе...»

НастройкаОписание
ПеременнаяКакую переменную проверять
ВеткиСписок условий с целевыми узлами

Операторы: равно, не равно, содержит, не содержит, больше, меньше, пусто, не пусто, иначе.

Значения в условиях поддерживают переменные: {user.balance}, {item.price}.


📝 Установить переменную (set_variable)​

Задаёт, изменяет или вычисляет переменные.

Режимы lookup, str_replace, json_push и json_format поддерживаются общей схемой присваиваний и генератором.

РежимЧто делаетПример
ТекстПростая подстановка"Привет, {first_name}"
ВыражениеАрифметика (+, -, *, /, %)"{balance} - 50"
Случайное числоЧисло в диапазонеот 500 до 900
Случайный элементИз списка через запятую"🔧,💥,💡,⚡"
Элемент массиваПо индексу или ключуdata.users.0.name
TimestampТекущее время ± смещение"90" = сейчас + 90 сек
Форматирование времениСекунды → MM:SS"{expires} - {now}"
Поиск в таблицеLookup по условию—
Замена подстрокиstr_replace"старое" → "новое"
Добавить в массивjson_pushJSON-объект в массив
Форматировать массивjson_formatМассив → строка
Regex извлечениеИзвлечь по регулярке"(\d+)\s*руб" группа 1 → "7216970"
Число из строкиextract_number"{text}" → первое число
Разделить и взятьsplit_get"{email}" sep=@ idx=1 → домен
JSON по ключуjson_get"{api}" path=data.user.name
Подстрокаsubstring"{id}" start=0 end=8
Условие (если/иначе)conditionalесли {balance} > 1000 → "💰", иначе → "💸"
Нижний регистрlowercase"{text}" → все строчные
Верхний регистрuppercase"{text}" → все заглавные
Убрать пробелыtrim" текст " → "текст"
Длинаlength"[1,2,3]" → "3"
Объединение массивовarray_concatvalue: "{arr1}", concatWith: "{arr2}" → склеенный массив

Сохранение в PostgreSQL (persistToDb)​

Запись пользовательской переменной через update_user_data_in_db выполняется одним атомарным запросом: создаёт пользователя при необходимости и обновляет выбранный ключ, сохраняя остальные данные. При временной ошибке соединения бот делает до трёх попыток, заново получая соединение из пула, с паузами 0,5 и 1 секунду. Ошибки данных и SQL не повторяются. Если восстановление удалось, в журнале появляется предупреждение; если все попытки неуспешны — ошибка, и функция возвращает False. Журнал содержит тип ошибки и номер попытки, без пользовательских значений. Настройка выполняется автоматически при включённой пользовательской БД; произвольные запросы узла psql_query этим механизмом не охватываются.

У каждого присваивания в массиве assignments можно включить флаг persistToDb: true. После записи значения в user_data оно дополнительно сохраняется в PostgreSQL через set_user_var — переменная переживает перезапуск бота (при включённой пользовательской БД).

{ "id": "a1", "variable": "reputation", "value": "{reputation} + 10", "mode": "expression", "persistToDb": true }

ℹ️ Работает только при включённой пользовательской БД бота. Без неё флаг игнорируется.


🛑 Стоп обработки (stop_processing)​

Устанавливает user_data[user_id]['_stop_processing'] = True для текущего пользователя. Используется вместе с incoming_message_trigger и включённым imtStopOnFlag: middleware триггера увидит флаг и не вызовет следующий handler в цепочке aiogram для этого апдейта.

НастройкаОписание
АвтопереходСледующий узел после установки флага (enableAutoTransition: true)

Флаг сбрасывается автоматически при следующем проходе middleware incoming_message_trigger.


📊 Счётчик частоты (rate_counter)​

In-memory счётчик событий в скользящем временном окне (deque по ключу). Каждый вызов узла добавляет метку времени и удаляет устаревшие записи за пределами окна.

Полностью истёкшие ключи очищаются небольшими порциями при обращении к счётчику. Живые события не удаляются ради ограничения памяти; при полной тишине фоновая очистка проверяет до 256 ключей примерно раз в секунду. Очистка пользовательских данных сохраняет отдельный часовой интервал и TTL 3600 секунд. Большой набор ключей очищается за несколько проходов. Для обновления существующего бота нужны повторная генерация и перезапуск.

НастройкаПолеОписание
Ключ счётчикаcounterKeyУникальный ключ (поддерживает {переменные}, напр. {user_id})
ОкноwindowSecondsРазмер окна в секундах (по умолчанию 60)
РезультатsaveResultToИмя переменной для количества событий в окне (по умолчанию rate_count)
АвтопереходautoTransitionToСледующий узел (enableAutoTransition: true)

Ограничения:

  • Данные хранятся только в памяти процесса — при перезапуске бота счётчики обнуляются
  • Типичное применение: антиспам, лимиты действий, детекция флуда

🧮 Inline-выражения ({=...})​

Вычисление формул прямо в тексте сообщения без отдельной ноды set_variable.

СинтаксисРезультатОписание
{=credits - price}3800Арифметика
{=thousands(credits)}5 000Форматирование числа
{=round(score / games, 2)}70.58Округление
{=max(hp - damage, 0)}0Функции min/max

Поддерживается в любом текстовом поле: messageText, mediaCaption, button text, httpRequestUrl и т.д.

Доступные функции: round, abs, int, float, min, max, str, reversed, thousands.

Доступные методы строк: replace, strip, lstrip, rstrip, lower, upper, startswith, endswith, split, join, count, find, format и проверки isdigit, isnumeric, isdecimal, isalpha, isalnum, isspace.

Генераторы и comprehension не поддерживаются — такое выражение вернётся как текст, не вычислившись. Чтобы отсечь нечисловой ввод, применяйте isdigit, а не comprehension.

При ошибке или невалидном выражении текст {=...} остаётся без изменений (бот не падает).


🔄 Цикл (loop)​

Итерация по массиву данных.

При сборе узлов цикла генератор пропускает пустые записи массива и продолжает поиск целевых узлов. В корректном проекте массив узлов должен содержать только ноды.

НастройкаОписание
ИсточникПеременная с массивом
Переменная элементаИмя для текущего элемента (доступ: {item.field})
Переменная индексаИмя для номера итерации (0, 1, 2...)
ПараллельноВыполнять все итерации одновременно
ЗадержкаПауза между итерациями (сек)
ЛимитМаксимум итераций (0 = без лимита)
Тело циклаПервый узел внутри цикла
После циклаУзел после завершения

⚡ Параллельная группа (parallel_split)​

Одновременный запуск нескольких независимых веток сценария (fan-out). Каждая ветка — отдельная asyncio-задача; точки сбора (join) нет.

НастройкаОписание
ВеткиСписок веток: подпись + стартовая нода. Каждая ветка — отдельный порт на холсте
При ошибке веткиНеобязательная нода-фоллбек (обычно setv-инкремент, чтобы сбор не завис)
Максимум одновременныхЛимит веток через Semaphore (0 = без лимита, по умолчанию 5) — защита от FloodWait
Не запускать повторноВключено по умолчанию — повторное нажатие блокируется, пока прогон не завершён
Ждать завершения всехПо умолчанию выключено (fire-and-forget)

Сбор результатов (join без join-ноды): каждая ветка в конце инкрементит счётчик (set_variable, mode=expression — инкремент атомарный), а condition проверяет «все ли финишировали» — последняя завершившаяся ветка запускает итог.

Ограничения:

  • Внутри веток нельзя использовать input — FSM один на пользователя
  • Триггеры внутри веток не имеют смысла (это точки входа)
  • Сброс счётчика сбора ставить до ноды, не внутри веток

Подробная концепция и сценарии: docs/futures/nodes/parallel-split-node.md.


⏱️ Задержка (delay)​

Пауза перед следующим действием.

НастройкаОписание
ВремяЗначение (поддерживает {переменные}; для секунд — дробные, напр. 0.1)
ЕдиницаСекунды / минуты / часы / дни / недели
РежимБлокирующий — ждёт, потом продолжает. Фоновый — текущая цепочка завершается, переход через N времени

⭐ Выставить счёт (send_invoice)​

Выставляет счёт в текущий чат: XTR (звёзды) или фиат (ISO 4217 из Telegram Payments, напр. EUR / RUB / USD / GBP / UAH…) через провайдера из BotFather → Payments. При добавлении рядом создаётся узел Клавиатура с одной кнопкой «Оплатить» (action: pay, нельзя убрать); можно добавить «Отмена» и другие ниже. Связь — через keyboardNodeId. Без клавиатуры кнопку оплаты нарисует Телеграм сам. Клавиатура только inline. Переход по autoTransitionTo — после успешной оплаты, не с кнопки «Оплатить».

НастройкаОписание
ВалютаinvoiceCurrency: XTR или код из Bot Payments; либо {переменная} (код подставится при оплате)
Токен провайдераПри не-XTR: invoiceProviderSource = inline (поле invoiceProviderToken) или env (invoiceProviderTokenEnv, напр. PAYMENT_PROVIDER_TOKEN). Для продакшена предпочтителен env — при inline токен попадает в project.json и код
НазваниеЗаголовок карточки, 1–32 знака (invoiceTitle)
ОписаниеТекст под названием, 1–255 (invoiceDescription)
ЦенаОдна сумма invoiceAmount или список invoicePrices: [{ id, label, amount }, …] → несколько LabeledPrice. Для XTR — только одна строка. Label/amount допускают {переменные}
КартинкаinvoicePhotoUrl (+ опц. invoicePhotoSize / Width / Height); для /uploads/ бот добавит API_BASE_URL
Скрытая меткаPayload покупки; пусто = id узла (invoicePayload)
Защита / deep-linkinvoiceProtectContent → protect_content; invoiceStartParameter
Доставка в чатТолько этот узел: invoiceMessageThreadId, invoiceDirectMessagesTopicId, тихая отправка, reply-to, effect, paid broadcast, JSON invoiceSuggestedPostParams
Сохранить суммуИмя переменной для total_amount (savePaymentAmountTo)
Сохранить код покупкиИмя переменной для telegram_payment_charge_id (savePaymentChargeIdTo)
Запросить контактыТолько фиат: invoiceNeedName / Email / Phone / Shipping; invoiceIsFlexible; send_*_to_provider; tips (invoiceMaxTipAmount, invoiceSuggestedTipAmounts); invoiceProviderData
Сохранить order_infoПосле оплаты: saveOrderNameTo / saveOrderEmailTo / saveOrderPhoneTo ← order_info
КлавиатураПри добавлении — соседний keyboard с «Оплатить»; стрелка «после оплаты» от кнопки pay
Следующий узелautoTransitionTo + enableAutoTransition: true — после оплаты
Проверка перед оплатойpreCheckoutMode: always (по умолчанию, всегда ok=True) или condition

Если в проекте есть хотя бы один send_invoice или create_invoice_link, бот отвечает на pre_checkout_query (лимит Телеграма — 10 секунд). Без режима condition ответ по-прежнему «да». Подписка на 30 дней в чат не ставится — Telegram отвечает SUBSCRIPTION_EXPORT_MISSING; только узел «Ссылка на счёт» и только при XTR. Отмена автопродления — узел edit_star_subscription.

Отказ до списания. preCheckoutMode: "condition" проверяет переменную пользователя прямо в обработчике pre_checkout, без ожидания ввода. Если условие ложно — answerPreCheckoutQuery с ok=False и error_message из preCheckoutErrorMessage (подстановка {переменных}, пустой текст → «Оплата сейчас недоступна», обрезка до 200 знаков). Если истинно — ok=True, оплата и переход после успеха не меняются. Операторы те же, что у condition для переменной: filled, empty, equals, not_equals, contains, not_contains, starts_with, ends_with, matches_regex, greater_than, less_than, between (preCheckoutValue2), is_even, is_odd, divisible_by. Системные (is_private, подписка и т.п.) здесь не используются. При нескольких счетах правило выбирается по payload: сначала запись этой сессии (_stars_payment_targets), иначе статическая карта для метки без {переменных}. Метка с {переменными} проверяется в том же запуске бота, после создания счёта. Неизвестный оператор не включает отказ — счёт подтверждается.

Для ботов с цифровой оплатой Телеграм требует /paysupport (и обычно /terms) — сделайте их обычными command_trigger + message.


Создаёт URL оплаты через Bot API createInvoiceLink (не карточку в чат). Те же валюта и токен провайдера, что у send_invoice. Кладёт ссылку в переменную и сразу идёт на autoTransitionTo. После оплаты — afterPaymentTo (если сессия ещё жива) или триггер «Успешная оплата».

НастройкаОписание
Валюта / токенКак у send_invoice (invoiceCurrency, invoiceProviderSource, …)
Название / описание / цена / картинка / меткаКак у send_invoice (invoicePrices, photo sizes)
Подписка на 30 днейinvoiceSubscription: true → subscription_period: 2592000. Только при XTR
Сохранить ссылкуsaveInvoiceLinkTo — имя переменной под URL
Сохранить сумму / кодПосле оплаты (savePaymentAmountTo, savePaymentChargeIdTo)
Запросить / сохранить контактыКак у send_invoice (need_, shipping, flexible, tips, provider_data, send__to_provider) — только фиат
Без полей чатаНет protect / start_parameter / thread / silent / reply (только у send_invoice)
После созданияautoTransitionTo + enableAutoTransition — сразу после URL
После оплатыafterPaymentTo — опционально
Проверка перед оплатойТе же preCheckoutMode / preCheckoutVariable / preCheckoutOperator / preCheckoutValue / preCheckoutValue2 / preCheckoutErrorMessage, что у send_invoice. Общий pre_checkout выбирает правило по payload

Клавиатуру pay не создаёт. Типичная цепочка: /buy → create_invoice_link → message без голого URL + inline url-кнопка «Оплатить» с {invoice_url}.

При фиате runtime читает те же shop_need_name / email / phone / shipping / photo, что и send_invoice (переменные после multi-select магазина).


⭐ Успешная оплата (successful_payment_trigger)​

Срабатывает, когда пришла оплата звёздами, а счёт не из текущей сессии бота (ссылка, старый счёт после рестарта, другой поток). Если payload есть в _stars_payment_targets текущего запуска — сработает выход у send_invoice / create_invoice_link, не этот триггер.

Один общий обработчик successful_payment: сначала счёт/ссылка, иначе первый подходящий триггер. Порядок фильтров: exact → starts_with → all. pre_checkout — при наличии send_invoice или create_invoice_link.

НастройкаОписание
Фильтр меткиpayloadFilter: all / exact / starts_with
Значение меткиpayloadValue — для exact / starts_with
Сохранить суммуsavePaymentAmountTo
Сохранить код покупкиsavePaymentChargeIdTo
Следующий узелautoTransitionTo (порт trigger-next)

↩️ Вернуть звёзды (refund_stars)​

Возвращает звёзды покупателю за покупку в этом боте по коду telegram_payment_charge_id (метод refundStarPayment). Не путать с «отправить звёзды» — произвольного перевода нет.

НастройкаОписание
КомуТекущий пользователь или ID / {переменная} (refundUserSource, refundUserId)
Код покупкиСтрока с {переменными} (refundChargeId) — обычно из savePaymentChargeIdTo у счёта
УспехautoTransitionTo + enableAutoTransition: true
Пустой кодвыход refundEmptyTarget (порт «Пустой код»)
Код не найденвыход refundNotFoundTarget (CHARGE_NOT_FOUND и прочие)
Уже возвращёнвыход refundAlreadyRefundedTarget (CHARGE_ALREADY_REFUNDED)
Fallback-текстыrefundMsg* — только если соответствующий выход не подключён
Без выхода ошибкиignoreErrors — после fallback-текста идти на «Успех»

Типично: /back КОД → refund_stars → сообщения по выходам. Демо на 1⭐: после оплаты сразу refund_stars с {payment_charge_id}. Бот не делает raise при ошибке возврата.


🔄 Подписка за звёзды (edit_star_subscription)​

Отменяет или снова разрешает автопродление Stars-подписки (Bot API editUserStarSubscription). Не возврат денег и не выставление счёта — нужен telegram_payment_charge_id из оплаты подписки (ссылка с invoiceSubscription).

НастройкаОписание
ДействиеsubscriptionAction: cancel → is_canceled=True; enable → False
КомуТекущий / ID / {var} (subscriptionUserSource, subscriptionUserId)
Код покупкиsubscriptionChargeId
УспехautoTransitionTo + enableAutoTransition
Пустой кодsubscriptionEmptyTarget
Ошибка APIsubscriptionErrorTarget
Fallback-текстыsubscriptionMsgEmpty, subscriptionMsgError
ignoreErrorsПосле текста ошибки идти на успех, если задан

Типично: /cancel_sub → edit_star_subscription (cancel, {payment_charge_id}) → сообщение.


💰 Баланс звёзд (get_star_balance)​

Запрашивает баланс звёзд на счету бота (Bot API getMyStarBalance), не баланс пользователя. Целое amount (может быть отрицательным) сохраняется в переменную.

НастройкаОписание
Сохранить балансsaveStarBalanceTo (по умолчанию star_balance)
УспехautoTransitionTo + enableAutoTransition
Ошибка APIbalanceErrorTarget
Fallback-текстbalanceMsgError
ignoreErrorsПосле текста ошибки идти на успех, если задан

Типично: /balance → get_star_balance → сообщение «На боте {star_balance} ⭐»; перед refund_stars — condition по балансу.


💻 Python-код (code)​

Выполняет произвольный Python как тело async-функции. Позволяет заменить длинную цепочку userbot-нод одним узлом.

НастройкаОписание
СкриптAsync-тело с await (не оборачивать в async def самому)
Следующий узелautoTransitionTo + enableAutoTransition: true

Пространство имён​

ИмяЧто это
переменные пользователяДоступны просто по имени, без user_data[...]
client, userbot_clientTelethon-клиент юзербота (тот же, что у userbot-узлов)
botЭкземпляр aiogram Bot — правка сообщений самого бота, прогрессбар
user_idID текущего пользователя
user_dataПеременные текущего пользователя
all_user_dataВсе пользователи: all_user_data[user_id]
set_user_varФункция записи переменной пользователя
callback_query, stateОбъекты aiogram текущего апдейта, могут быть None
asyncio, json, re, math, datetime, logging, Button, eventsПредзагруженные модули

Возврат значений​

Присваивание на верхнем уровне кода автоматически сохраняется в переменные пользователя:

lucky_payment = 8942

Внутри вложенных функций локальное присваивание наружу не попадёт — пишите напрямую в _ns:

async def _worker():
_ns['lucky_payment'] = 8942

set_user_var во вложенных функциях недоступна, только _ns[...].

Не сохраняются: имена, начинающиеся с _, предзагруженные модули, функции и классы.

Прогрессбар​

bot в пространстве имён позволяет обновлять сообщение по ходу работы — полезно, когда узел опрашивает много внешних ботов:

await bot.edit_message_text('Опрашиваю… 3/15', chat_id=chat_id,
message_id=progress_msg_id, parse_mode='HTML')

Ограничения и грабли​

  • Таймаут 180 секунд на весь узел; ошибка не рвёт переход дальше.
  • Нужны USERBOT_API_ID, USERBOT_API_HASH, USERBOT_SESSION_STRING, иначе Telethon-вызовы не сработают (остальной Python — сработает).
  • Self-hosted: полный доступ к сессии аккаунта, без RestrictedPython.
  • Одна userbot-сессия на процесс. Не запускайте параллельно внешний скрипт на той же сессии — Telegram может разлогинить одного из клиентов.
  • asyncio.wait_for начинает отсчёт сразу. Если задачи ждут в очереди семафора, таймаут выгорит на ожидании — ставьте wait_for внутри воркера, а не вокруг всей очереди.
  • MessageButton.click() у Telethon ждёт answerCallbackQuery и падает с BotResponseTimeoutError, если бот не отвечает. Оборачивайте в asyncio.wait_for(..., timeout=2..3).

📥 Ожидание ввода (input)​

Единственный способ сбора ввода. Message-нода только отправляет вопрос, input-нода ожидает и сохраняет ответ.

⛔ Поля collectUserInput, inputVariable в message-ноде — запрещены. Используйте только отдельную ноду input.

Совместимость старых проектов: миграция legacy-сбора выполняется только при явном collectUserInput: true. Оставшиеся inputTargetNodeId, inputVariable, inputRequired и другие параметры сами по себе ввод не включают. При выключенном сборе сообщение остаётся сообщением с прежней клавиатурой; input-переход для него не отображается и не участвует в генерации. Для отдельной input-ноды inputTargetNodeId продолжает задавать переход после ответа.

Типичные цепочки:

  • Текстовый ввод: message → input → следующий узел
  • Кнопочный ввод: message → keyboard → input (callback) → следующий узел
НастройкаОписание
ПеременнаяКуда сохранить ответ
Тип вводаТекст / фото / видео / аудио / документ / геолокация / контакт / callback
Следующий узелКуда перейти после получения ответа
Режим записиЗаменить / добавить к существующему
Валидацияemail / phone / number / min-max длина
Сохранить метаданныеДополнительные переменные с информацией о файле
Таймаут ожиданияinputTimeout — лимит в секундах (число > 0). По истечении FSM сбрасывается, пользователю отправляется inputTimeoutMessage
Сообщение при таймаутеinputTimeoutMessage — текст при истечении (по умолчанию «Время ожидания истекло.»)

Отмена формы (/cancel)​

При активном сборе ввода бот автоматически регистрирует команду /cancel. По ней вызывается _clear_form_session — FSM сбрасывается, пользователь получает ответ «Форма отменена.». Обработчик генерируется только если в проекте есть хотя бы одна input-нода с активным сбором ввода.

Метаданные медиа​

При включении "Сохранить метаданные медиа" для медиа-типов (фото, видео, аудио, документ) бот автоматически создаёт дополнительные переменные с суффиксами:

СуффиксОписаниеТипы
_file_idTelegram file_idвсе
_file_unique_idУникальный ID файлавсе
_thumbnailfile_id обложкивидео, аудио, документ
_durationДлительность (сек)видео, аудио
_file_sizeРазмер файла (байт)все
_file_nameИмя файлавидео, аудио, документ
_width / _heightРазмеры (px)фото, видео
_mime_typeMIME типвидео, аудио, документ
_title / _performerНазвание / исполнительаудио
_small_file_idfile_id миниатюрыфото
_all_sizesJSON всех размеровфото

Пользователь может выбрать какие метаданные сохранять и задать кастомные имена переменных.


✏️ Действия с сообщениями​

УзелЧто делает
delete_messageУдаляет одно или несколько сообщений
pin_messageЗакрепляет сообщение в чате
unpin_messageОткрепляет сообщение
answer_callback_queryВсплывающее уведомление при нажатии inline-кнопки

🗑️ Удалить сообщение (delete_message)​

Полноценная action-нода для удаления сообщений. Может стоять в любом месте сценария.

НастройкаОписание
Источник сообщенияcurrent_message — текущее сообщение пользователя
last_bot_message — последнее сообщение бота
reply_message — сообщение, на которое ответили (reply)
range_from_reply — все сообщения от reply до текущего (пург)
last_n — последние N сообщений (по диапазону ID)
custom — указать ID вручную или через {переменную}
Чатcurrent_chat — текущий чат
custom — указать ID чата или {переменную}
Игнорировать ошибкиНе прерывать сценарий если сообщение не найдено (вкл по умолчанию)
Массовое удалениеПеременная с JSON-массивом message_id (до 100 за вызов, автобатчинг)
АвтопереходСледующий узел

Ограничения Telegram:

  • Бот удаляет чужие сообщения только в группах (нужны права can_delete_messages)
  • Сообщения старше 48 часов удалить нельзя
  • В личных чатах бот удаляет только свои сообщения
  • deleteMessages принимает до 100 ID за один вызов (автоматически разбивается на батчи)

👥 Действия с пользователями (группы)​

УзелЧто делает
ban_userЗабанить пользователя в группе
unban_userРазбанить
mute_userЗамутить (запретить писать)
unmute_userРазмутить
kick_userИсключить пользователя из группы
promote_userНазначить администратором
demote_userСнять права администратора
admin_rightsУстановить конкретные права

🔇 Ограничить участника (mute_user)​

Запрещает выбранные действия в группе через restrictChatMember.

ПолеОписание
durationДлительность в секундах
canSendMessagesТекст
canSendAudiosАудио
canSendDocumentsДокументы
canSendPhotosФото
canSendVideosВидео
canSendVideoNotesВидеозаметки (кружки)
canSendVoiceNotesГолосовые
canSendPollsОпросы
canSendOtherMessagesСтикеры и GIF
canAddWebPagePreviewsПредпросмотр ссылок
canReactToMessagesРеакции
canChangeGroupInfoОписание группы
canInviteUsers2Приглашения
canPinMessages2Закрепление

Новый блок ограничения по умолчанию запрещает все эти действия.

🔊 Снять ограничение (unmute_user)​

Снова разрешает текст, все виды медиа, опросы, стикеры, предпросмотр ссылок и реакции. Смену описания группы, приглашения и закрепление не открывает.

👑 Назначить администратора (promote_user)​

Передаёт в promoteChatMember права, которые заданы в блоке. Новые права попадают в вызов только если они уже есть в данных: canManageChat, canPostMessages, canEditMessages, canManageDirectMessages, canManageTags, canSendWelcomeMessages. Незаданные права не добавляются.

⬇️ Снять администратора (demote_user)​

Вызывает promoteChatMember и передаёт False для всех boolean-прав ChatAdministratorRights: анонимность, управление чатом, удаление, видеочаты, ограничения, назначение администраторов, описание, приглашения, истории, публикация и редактирование сообщений, закрепление, темы, личные сообщения, теги и приветствия.

⚙️ Панель прав (admin_rights)​

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

👢 Исключить пользователя (kick_user)​

Исключает пользователя из группы через unbanChatMember(only_if_banned=False). Пользователь удаляется из чата, но может вернуться по ссылке-приглашению.

Для этого узла используется отдельный шаблон kick-user.

НастройкаОписание
Источник пользователяcurrent_user — отправитель сообщения-триггера
reply_user — автор сообщения, на которое ответили (reply)
custom — указать ID вручную или через {переменную}
Чатcurrent_chat — текущий чат
custom — указать ID чата или {переменную}
Игнорировать ошибкиНе прерывать сценарий если пользователь не в чате или бот не имеет прав (вкл по умолчанию)

Ограничения Telegram:

  • Бот должен быть админом с правом «Блокировка участников» (can_restrict_members)
  • Работает только в группах и супергруппах

🔗 Переменные​

Переменные используются в текстах, URL, SQL-запросах и других полях через синтаксис {имя}.

Системные переменные​

ПеременнаяОписание
{user_id}Telegram ID пользователя
{username}Username пользователя
{first_name}Имя
{last_name}Фамилия
{chat_id}ID чата (заполняется при входящем сообщении)
{chat_type}Тип чата: private, group, supergroup, channel
{message_text}Текст или подпись последнего сообщения
{message_type}Тип содержимого: text, photo, video, document и т.д.
{callback_data}Данные нажатой кнопки
{reply_to_user_id}ID автора сообщения, на которое ответили
{reply_to_username}Username автора сообщения-ответа
{reply_to_first_name}Имя автора сообщения-ответа
{reply_to_last_name}Фамилия автора сообщения-ответа
{reply_to_message_id}ID сообщения, на которое ответили
{reply_to_text}Текст сообщения, на которое ответили
{message_id}ID текущего сообщения

Переменные chat_id, chat_type, message_* и reply_to_* записываются функцией capture_message_context в обработчиках команд, текста, входящих сообщений и триггера участника (member_trigger). Переменные reply_to_* заполняются только если входящее сообщение является ответом (reply) на другое сообщение; иначе остаются пустыми.

Пользовательские переменные​

Создаются через узлы set_variable, bot_table, http_request, psql_query и сбор ввода.

Вложенный доступ: {response.data.user.name}, {profile.balance}.


🔗 Связи между узлами​

Способ связиОписание
АвтопереходАвтоматический переход после выполнения узла
Кнопка → узелПереход при нажатии кнопки
Ветка условия → узелПереход по результату проверки
После цикла → узелПереход после завершения всех итераций

💡 Рекомендации​

Когда использовать bot_table vs psql_query​

ЗадачаРекомендация
Простой CRUD (профиль, баланс)bot_table — проще и безопаснее
Атомарный increment/decrementbot_table — встроено
Сложные JOIN и аналитикаpsql_query
Автосоздание таблицыbot_table

Типичные паттерны​

  • Регистрация: /start → upsert профиля → приветствие
  • Профиль: /profile → чтение из таблицы → вывод данных
  • Магазин: меню с кнопками → проверка баланса → списание → подтверждение
  • Расписание: триггер по времени → SQL-обновление → отчёт в админ-чат

🟣 Юзербот (Telethon)​

Узлы для работы через аккаунт пользователя (Telethon MTProto). Работают параллельно с основным ботом.

📤 Сообщение юзербота (userbot_message)​

Отправляет сообщение от аккаунта пользователя через Telethon. Поддерживает текст, медиа, переменные, несколько получателей.

НастройкаОписание
ПолучателиСписок entity: @username, числовой ID, {переменная}, 'me'
Текст сообщенияПоддерживает {переменные} и HTML/Markdown форматирование
МедиафайлыЛокальные файлы (/uploads/) или URL
Режим форматированияhtml, markdown, none
Отключить превью ссылокНе показывать превью URL в сообщении
Сохранить ID сообщенияПеременная для message_id отправленного сообщения
Сохранить ID ответаПеременная для ID ответного сообщения от получателя (saveResponseIdTo)
Сохранить текст ответаПеременная для текста ответного сообщения (saveResponseTextTo)
Сохранить кнопки ответаПеременная для кнопок ответа как JSON-массив [{text, type, data?, url?}] (saveButtonsTo)
АвтопереходУзел для перехода после отправки

Ограничения:

  • Кнопки (inline/reply) НЕ поддерживаются — ограничение Telegram для user-аккаунтов
  • File_id от Bot API нельзя использовать в Telethon (разные сессии)
  • Лимит: ~50 сообщений/день разным пользователям, 1 сообщение/секунду в один чат
  • Встроенная защита от FloodWait с автоматическим retry

Требования:

  • В настройках бота должен быть включён и авторизован Telethon Userbot
  • Переменные USERBOT_API_ID, USERBOT_API_HASH, USERBOT_SESSION_STRING в .env

👆 Нажать кнопку (userbot_click_button)​

Нажимает inline-кнопку в сообщении через Telethon. Поддерживает поиск по тексту, callback_data или индексу.

НастройкаОписание
Entity (чат)Чат где находится сообщение с кнопками
Источник сообщенияПоследнее сообщение / Конкретный ID
Message IDID сообщения (если конкретный)
Способ поискаПо тексту кнопки / По callback_data / По индексу (row, col)
ЗначениеТекст кнопки / callback_data / "0, 1"
Сохранить alertПеременная для текста alert от бота
Сохранить текстПеременная для текста обновлённого сообщения
Сохранить кнопкиПеременная для JSON массива кнопок (с типами)
Сохранить медиаПеременная для медиа-объекта
АвтопереходУзел для перехода после нажатия

Источник last_message, который может быть установлен общей схемой при отсутствии поля, трактуется как last: юзербот ищет последнее сообщение. Для явно указанного ID используйте manual и поле messageId.

Формат JSON кнопок:

[
{"text": "Купить", "data": "buy_btc", "type": "callback"},
{"text": "Сайт", "url": "https://...", "type": "url"},
{"text": "Меню", "type": "text"}
]

Типы кнопок: callback, url, text, switch_inline, web_app, request_phone, request_location, buy, game


🔍 Inline-запрос (userbot_inline_query)​

Выполняет inline-запрос к боту (@bot query) и отправляет выбранный результат в чат.

НастройкаОписание
Bot usernameUsername бота для inline-запроса
QueryТекст запроса (поддерживает {переменные})
Отправить в тот же чатРезультат отправляется в чат с ботом
Целевой чатКуда отправить (если не в тот же)
Индекс результатаКакой результат выбрать (0 = первый)
Сохранить titleПеременная для заголовка результата
Сохранить descriptionПеременная для описания результата
Сохранить ID сообщенияПеременная для ID отправленного
АвтопереходУзел для перехода после отправки

🧰 Утилиты​

📝 Комментарий (comment)​

Текстовая заметка-стикер на холсте. Не влияет на логику бота — игнорируется при генерации Python-кода. Не имеет портов, связей и автоперехода.

Используется для пояснений к сценарию: помечать воронки, ветки, TODO, описывать назначение групп нод.

НастройкаОписание
Текст заметкиПроизвольный текст (поддерживает многострочный ввод)
Цвет заметкиyellow, blue, green, pink, gray — для визуального разделения зон

Содержит только два поля — messageText и commentColor. Служебных полей сообщения (buttons, keyboardType и т.п.) у ноды нет.

Ограничения:

  • Не генерирует кода и не участвует в потоке выполнения
  • Соединить с другими нодами нельзя (нет портов)
  • При авто-раскладке остаётся отдельным свободным блоком
  • В превью на холсте отображается до 200 символов

Системные события блокировки​

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

Отдельная настройка «Отслеживать группы» пишет в ленту группы «Бота добавили в группу» и «Бота исключили из группы». Каналы не обрабатываются. Это тоже не нода сценария.

Размер исходящих фотографий в истории​

При включённой истории сообщений размер отправленного фото сохраняется из ответа Telegram вместе с file_id и размерами изображения. Это работает для отправки через bot.send_photo, message.answer_photo и вспомогательную функцию send_photo_with_caption. Используется наибольший вариант фото, возвращённый Telegram. Если размер отсутствует в ответе, в истории остаётся неизвестное значение, а не 0 байт.

У видеообложки сохранённый thumbnailFileIds имеет приоритет перед thumbnailUrls. При отправке с готовым file_id бот не сохраняет запасной URL обложки в кэш.

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

При включённой базе пользователей и истории исходящие сообщения message-блока сохраняют ID блока отправки: текст, фото, видео, аудио, документы, отправка через Telegram-кэш и альбомы. У каждого элемента альбома собственная запись истории с общим ID блока. Для появления связи у работающего бота требуется заново сгенерировать код и перезапустить его; старые записи без ID блока не изменяются. Поиск «Используется в сценарии» отдельно проверяет ссылки на файл в текущих блоках.

Аналитика действий бота​

При включённой базе пользователей каждый настоящий Telegram message/callback записывается один раз независимо от количества пройденных нод. Личное сообщение, принятая команда, команда текущему боту в группе, ответ на его сообщение и реальное нажатие кнопки подтверждают взаимодействие. Обычная групповая переписка учитывается отдельно от аудитории бота.

Отправленные тексты и медиа считаются после успешной отправки; элементы альбома сохраняют реальный групповой чат. Нажатия кнопок и изменения доступности — отдельные виды событий. Групповое сообщение не снимает блокировку личного диалога. Очистка переписки не удаляет сохранённые аналитические факты.

Пересылка и копирование сообщения тоже входят в исходящие, включая массовые операции. Повторная запись обёрткой или переход через несколько блоков не увеличивает итог; редактирование старого сообщения не является новой отправкой. Настоящее нажатие сохраняет отдельный callback_query_id. Некорректная команда вида /bad!@bot сама по себе не добавляет человека в аудиторию.

Для точного учёта копий используйте числовой ID адресата. Если указан @username, генератор после успешной отправки пытается определить числовой ID чата за 2 секунды. Отказ этого запроса не ломает доставку или сценарий, но такая отправка не попадает в аналитику; в журнале появляется предупреждение. Когда известен отрицательный ID, но неизвестен тип чата, сохраняется «тип не определён», без предположения, что это группа.

Рекламные метки разрешаются по реестру. Связь реферала подтверждается отдельно; наличие ref_ в произвольной строке не является подтверждением. Коды link_ скрываются в стандартном хранении. При настроенном сохранении аргументов в переменную исходное значение доступно сценарию только в памяти во время обработки авторизации.

После обновления шаблонов требуется повторная генерация и перезапуск бота. Покрытие новой методики начинается с обновлённой сборки; старые профили не объявляются новыми пользователями.

При временном сбое аналитические факты остаются в приватной локальной очереди и доставляются после восстановления, в том числе после перезапуска. Тексты сообщений и коды входа в неё не попадают. Для постоянной работы в контейнере администратор задаёт ANALYTICS_SPOOL_DIR на постоянном томе; по умолчанию очередь хранится в домашней папке .botcraft/analytics-spool. Очередь сохраняет аналитику, но не восстанавливает пропущенные тексты переписки.

Хранение вложений​

Вложения проекта сохраняют логический адрес /uploads/.... При смене выбранного хранилища уже прикреплённые файлы остаются в прежнем хранилище. Доступ бота к исходникам ограничен его проектом; для работы с удалённым хранилищем необходимо настроить доступ к панели, как описано в инструкции по S3. Отправка альбома требует доступности всех его элементов. Запуск бота сам по себе не подтверждает отправку медиа.

При запуске нового сгенерированного кода воркер ждёт проверки Telegram и получения доступной Redis-блокировки в режиме polling. При недоступном Redis защита от двойного запуска отсутствует; старый сгенерированный код использует прежнее подтверждение запуска.

Резервная отправка текста при отсутствующем медиа​

Если исходник вложения не найден (MediaNotFoundError), узел message отправляет непустой текст без медиа, сохраняя форматирование, кнопки, тему чата и привязку к узлу в истории. Это происходит только до первой успешной отправки в данный чат: сбой дополнительных вложений или клавиатуры альбома не создаёт повтор основного сообщения. При пустом тексте, ошибке доступа, таймауте и сбое хранилища резервная отправка не выполняется; ошибка остаётся в журнале. Сохранённый Telegram file_id используется первым; если он устарел, бот пробует исходник и только при его подтверждённом отсутствии переходит к тексту.

Внешний polling-приёмник и сон обработчика​

Это серверный режим исполнения без новых полей нод и без переключателя в интерфейсе. Обычный polling и webhook сохраняются. Внешний приёмник получает обновления отдельно, а сгенерированный обработчик принимает их через внутренний протокол _WORKER_EXTERNAL_UPDATES_PROTOCOL и _worker_external_updates(dp, bot, stop_event). Обработчик проходит startup/shutdown aiogram и закрывает свои подключения при засыпании. Сон не публикует событие логической остановки бота; сообщения из внешней надёжной очереди не отбрасываются стандартным фильтром возраста сообщения.

Сборки с schedule_trigger, api_trigger, userbot, фоновым delay, положительным inputTimeout или parallel_split без awaitAll помечаются как несовместимые со сном. Для них нужен постоянный обработчик: независимые таймеры и фоновые ветки нельзя терять при выгрузке модуля. Произвольный код, создающий собственные фоновые задачи, также требует постоянного режима. Не добавляйте служебные поля протокола в JSON сценария.

Внешний режим требует доступного Redis; при ошибке подключения обработчик не переходит на локальное хранилище. Протокол состояния _WORKER_EXTERNAL_STATE_PROTOCOL = 1 передаёт воркеру словарь user_data, включая ожидание пользовательского ввода. Произвольные глобальные переменные пользовательского кода не входят в этот контракт; такие сценарии требуют постоянного режима.

Защита доставки webhook​

Для webhook панель автоматически сохраняет индивидуальный секрет бота. Он передаётся Telegram и проверяется Python-обработчиком; ответ об успешной доставке выдаётся после обработки. Для polling секрет не требуется.

Webhook со сном использует общий вход Node и очередь обновлений. Временный Python-обработчик не регистрирует и не удаляет webhook, поэтому новые сообщения могут пробудить его после освобождения памяти. Сценарии с постоянной фоновой работой по-прежнему несовместимы со сном.