🧩 Типы узлов (блоков)
Пул 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 — перехватывает сообщение до стандартных обработчиков и передаёт управление целевому узлу.
| Настройка | Поле | Описание |
|---|---|---|
| Тип чата | imtChatTypeFilter | any — любой чат, private — только личные, group — группы и супергруппы |
| ID группы | imtGroupChatId | Фильтр по конкретной группе (без префикса -100). Активен при imtChatTypeFilter: "group" |
| Источник ID группы | imtGroupChatIdSource | manual — значение из 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).
| Настройка | Поле | Описание |
|---|---|---|
| Тип события | memberEventType | join — вход, leave — выход, both — оба события (два обработчика) |
| ID группы | groupChatId | Опциональный фильтр по чату (без префикса -100) |
| Источник ID группы | groupChatIdSource | manual — из groupChatId, variable — из groupChatVariableName |
| Переменные входа | saveJoinedUserIdTo, saveJoinedUsernameTo | ID и username вошедшего участника (по умолчанию joined_user_id, joined_username) |
| Переменные выхода | saveLeftUserIdTo, saveLeftUsernameTo | ID и 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-Type | application/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 тело запроса |
| Auth | none / 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_push | JSON-объект в массив |
| Форматировать массив | 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_concat | value: "{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-link | invoiceProtectContent → 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.
🔗 Ссылка на счёт (create_invoice_link)
Создаёт 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 |
| Ошибка API | subscriptionErrorTarget |
| 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 |
| Ошибка API | balanceErrorTarget |
| 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_client | Telethon-клиент юзербота (тот же, что у userbot-узлов) |
bot | Экземпляр aiogram Bot — правка сообщений самого бота, прогрессбар |
user_id | ID текущего пользователя |
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_id | Telegram file_id | все |
_file_unique_id | Уникальный ID файла | все |
_thumbnail | file_id обложки | видео, аудио, документ |
_duration | Длительность (сек) | видео, аудио |
_file_size | Размер файла (байт) | все |
_file_name | Имя файла | видео, аудио, документ |
_width / _height | Размеры (px) | фото, видео |
_mime_type | MIME тип | видео, аудио, документ |
_title / _performer | Название / исполнитель | аудио |
_small_file_id | file_id миниатюры | фото |
_all_sizes | JSON всех размеров | фото |
Пользователь может выбрать какие метаданные сохранять и задать кастомные имена переменных.
✏️ Действия с сообщениями
| Узел | Что делает |
|---|---|
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/decrement | bot_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 ID | ID сообщения (если конкретный) |
| Способ поиска | По тексту кнопки / По 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 username | Username бота для 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, поэтому новые сообщения могут пробудить его после освобождения памяти. Сценарии с постоянной фоновой работой по-прежнему несовместимы со сном.