← Все лид-магниты
Пошаговый гайд · MAX

Как за 7 шагов сделать бота в MAX для любого бизнеса

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

0 / 7 шагов

Подготовка · Системный промт

Вставляется один раз в начале нового чата с нейросетью

Активен

Это роль и правила работы для нейросети — задаёт формат ответов, стек технологий и специфику MAX API. Вставьте целиком в новый чат перед тем, как начнёте Шаг 1. Дальше все промты шагов вставляются в этот же чат, не открывайте новый.

system_prompt.txt
Ты — Senior Software Engineer по разработке чат-ботов для мессенджера
MAX (API https://platform-api2.max.ru). Помогаешь не-программисту
пошагово строить бота.

## Роли
- Ассистент: Senior SE. Думай системно, предлагай изолированные решения,
  не раздувай ядро.
- Пользователь: не программист. Выполняет готовые команды, копирует
  вывод, подтверждает риски и go-live.

## Обязательный формат ответа
📋 план → 🔍 смотрю → 💾 бэкап → 🔄 правка → ✅ проверка → ⚠️ риски.
Команды давай одним блоком для вставки в терминал. Один блок = одна
вставка. После FAIL — стоп, не продолжай молча.

## Правила работы с кодом
1. 1 шаг ≈ 1 файл. Сначала бэкап, потом правка, py_compile, просмотр,
   при необходимости restart сервиса.
2. Сначала смотри файл/якорь. Не выдумывай API, пути, названия методов.
3. Heredoc в чате/SSH обрезается. Используй Path.write_text,
   list-of-lines, replace с count==1. Якорь не найден — FAIL.
4. Не удаляй и не «упрощай» контент/тексты без моего OK.
5. Changelog — только факты сделанного, храни отдельно.

## Изоляция
Новую функцию (модуль опроса, отправку файла, уведомление админу)
выноси в отдельный файл/модуль (services/, handlers/) — не раздувай
основной bot.py.

## Стек
Python 3.10+, asyncio, библиотека maxapi (форк Werdset) в venv,
SQLite (check_same_thread=False), aiohttp, python-dotenv, systemd
для запуска как службы.

## Ключевые особенности MAX API и maxapi
- Long polling через dp.start_polling(bot).
- auto_requests=True — оставляй включённым всегда. Именно
  enrich_event привязывает event.bot = bot. При auto_requests=False
  события падают с RuntimeError: Bot не инициализирован.
- Bot.send_message(..., disable_link_preview=True) — отключает
  превью ссылок.
- Bot.edit_message(message_id, text, attachments, parse_mode).
- Bot.delete_message(message_id).
- Bot.get_upload_url(type=UploadType.FILE) + upload файла —
  для отправки чек-листа файлом.
- Inline keyboard: Attachment(type="inline_keyboard",
  payload=ButtonsPayload(buttons=[[...]])).
- MessageCallback: event.callback.payload,
  event.callback.user.user_id,
  event.message.recipient.chat_id, event.message.body.mid.
- MessageCreated: event.message.sender может быть None
  (сообщения из каналов). Всегда guard перед обращением к user_id.
- event.answer() может вызывать error.edit.invalid.message на
  старых callback — оборачивай в try/except.
- Dispatcher catch-all callback_unknown должен быть последним
  среди обработчиков.
- MAX ограничивает число вложений: обычно 1 FILE на сообщение.
- attachment.not.ready — maxapi автоматически ретраит с паузой
  (RETRY_DELAY=2с), это нормально, не баг.
- Канальные сообщения: chat_id отрицательный, sender=None —
  не пытайся получить user_id.

## Работа с состояниями (FSM)
- Отдельный модуль state_manager.
- Состояния и данные пользователя — в памяти на этом этапе.
- Всегда проверяй состояние перед обработкой входящего сообщения.
- При новом /start сбрасывай состояние пользователя, чтобы он не
  попал в середину старого опроса.

## Работа с файлами
- Для отправки файла: get_upload_url -> upload -> AttachmentUpload
  (token=...).
- Всегда предусматривай fallback: если upload не удался — отправь
  текстовую ссылку на файл вместо вложения.

## Безопасность
- Токен бота и ID админа — только в .env, не в коде.
- Не логируй токен целиком.

## Healthcheck после изменений
- systemctl is-active 
- journalctl -u  --since "10 min ago" | tail -100
- python -c "import config; print('ok')"
- py_compile изменённого файла
- При необходимости: systemctl restart 

## Что нельзя делать без явного запроса
- Менять тексты вопросов или содержимое чек-листа.
- Массово рассылать сообщения существующим пользователям.
- Удалять контент/ссылки без моего OK.
- Тестировать на реальных чужих пользователях вместо тестового чата.
1

Скелет и подключение к MAX

Бот запускается и отвечает на /start

Заблокировано
🔒 Сначала выполните Шаг 0

Без базы, без опроса — проверяем только, что бот в принципе связан с MAX и может ответить.

step_1.txt
Шаг 1. Создай минимальный бот для MAX:
- venv, установка библиотеки maxapi
- bot.py с dp.start_polling(bot)
- обработчик /start, который отправляет статичное приветствие
  (напишите своё, например: «Привет! Я помогу подобрать [услугу].
  Ответьте на 3 коротких вопроса»)
- config.py, который читает BOT_TOKEN из .env
- auto_requests=True обязательно

Дай план файлов, которые создашь, и команды для терминала одним
блоком.

✔ Что вы получите

Написали боту /start в MAX — пришло приветствие. Бот запущен локально на вашем компьютере, база и опросы пока не нужны.

⚠ Возможные проблемы

RuntimeError: Bot не инициализирован

auto_requests=True не установлен. Попросите нейросеть проверить и включить этот параметр в коде запуска бота.

Бот не отвечает вообще, ошибок нет

Проверьте, что BOT_TOKEN в .env указан верно и что config.py реально его читает — попросите добавить print для проверки.

Ошибка при установке maxapi

Проверьте, что venv активирован перед pip install — частая причина, что библиотека ставится не туда.

2

Опрос (FSM)

Бот задаёт вопросы по очереди и помнит ответы

Заблокировано
🔒 Сначала выполните Шаг 1

Здесь появляется логика диалога — бот должен вести пользователя по вопросам, не путая порядок и не забывая, на каком вопросе тот остановился.

step_2.txt
Шаг 2. Добавь модуль state_manager и сценарий опроса из 3 вопросов
после /start:
Вопрос 1: (напишите своё, например: «Как вас зовут?»)
Вопрос 2: (напишите своё, например: «Какой у вас телефон
для связи?»)
Вопрос 3: (напишите своё, например: «Какая услуга вас
интересует?»)

Требования:
- состояние пользователя хранится в памяти (state_manager),
  переход на следующий вопрос только после ответа на текущий
- при новом /start состояние сбрасывается, не продолжай старый опрос
- guard: если event.message.sender is None (сообщение из канала) —
  не обрабатывать как ответ пользователя

✔ Что вы получите

Прошли все 3 вопроса подряд — бот не путает порядок, ответы видно в консоли/логах после каждого шага.

⚠ Возможные проблемы

Бот продолжает спрашивать старое после нового /start

Состояние не сбрасывается. Попросите нейросеть явно обнулять state пользователя при каждом /start.

Ответ на вопрос 2 воспринимается как ответ на вопрос 1

Нет проверки текущего состояния перед обработкой сообщения — добавьте guard: сначала смотрим state, потом решаем, что это за ответ.

Ошибка при сообщении из канала

sender оказался None. Добавьте проверку sender is None перед любым обращением к user_id.

3

Память (SQLite)

Ответы сохраняются в базу, а не пропадают

Заблокировано
🔒 Сначала выполните Шаг 2

Без этого шага все ответы живут только в оперативной памяти — при первом же перезапуске бота они исчезают безвозвратно.

step_3.txt
Шаг 3. Добавь SQLite (check_same_thread=False) и таблицу leads:
id, user_id, name, contact, interest, created_at, status.
После ответа на последний вопрос — сохраняй запись в таблицу
и выводи в консоль подтверждение записи.
Дай схему таблицы отдельным сообщением.

✔ Что вы получите

Прошли опрос, перезапустили бота, прошли опрос ещё раз — в базе видно обе записи. Схема таблицы сохранена отдельно.

⚠ Возможные проблемы

После перезапуска данные исчезли

Хранение было в python-переменной, а не в БД. Проверьте, что INSERT реально пишется в SQLite, а не в словарь в памяти.

Ошибка database is locked

SQLite открыт без check_same_thread=False. Попросите проверить этот параметр в подключении к базе.

Запись не появляется в таблице

Проверьте, что после INSERT вызывается commit/save — без этого запись не фиксируется в файле базы.

4

Доставка чек-листа

Пользователь получает файл сразу после опроса

Заблокировано
🔒 Сначала выполните Шаг 3

Это и есть ценность, ради которой человек проходил опрос — важно, чтобы файл дошёл, а если не дошёл — чтобы был запасной вариант.

step_4.txt
Шаг 4. После сохранения лида в БД отправь пользователю чек-лист:
- get_upload_url(type=UploadType.FILE) -> upload ->
  AttachmentUpload(token=...) -> send_message с вложением
- если загрузка не удалась — отправь fallback: текстовое сообщение
  со ссылкой на файл (напишите свою ссылку или облачное хранилище)
- учти: MAX разрешает обычно 1 FILE на сообщение
- attachment.not.ready — это нормальный ретрай библиотеки
  (RETRY_DELAY=2с), не считать ошибкой

✔ Что вы получите

Прошли опрос — получили файл. Специально сломали ссылку на файл — убедились, что вместо тишины приходит fallback-сообщение.

⚠ Возможные проблемы

Файл не приходит, ошибок в логах нет

Проверьте, что токен из get_upload_url действительно передан в AttachmentUpload — частое место потери данных между шагами.

В логах attachment.not.ready

Это нормально — библиотека сама повторит попытку через пару секунд, не нужно ничего чинить.

Бот пытается отправить два файла сразу и падает

MAX разрешает 1 FILE на сообщение. Отправляйте вложения по одному, не пачкой.

5

Уведомление владельцу

Вы узнаёте о новом лиде сразу, без захода в базу

Заблокировано
🔒 Сначала выполните Шаг 4

Главная причина иметь такого бота — не пропустить ни одного лида. Этот шаг закрывает именно это.

step_5.txt
Шаг 5. Сразу после сохранения лида в БД отправь сообщение админу:
Bot.send_message(ADMIN_CHAT_ID, текст с именем, контактом
и интересом лида).
ADMIN_CHAT_ID — из .env, не хардкодить в коде.
Это одно целевое сообщение одному человеку, не рассылка —
массовых уведомлений здесь нет.

✔ Что вы получите

Прошли опрос сами как тестовый пользователь — вам как админу пришло сообщение с верными данными, и пришло ровно один раз.

⚠ Возможные проблемы

Сообщение не приходит

Проверьте ADMIN_CHAT_ID в .env — это должен быть именно ID чата, а не username.

Приходят два одинаковых уведомления

Отправка вызывается в двух местах кода. Попросите нейросеть найти дубль вызова функции.

В уведомлении пустые поля

Уведомление отправляется раньше, чем собраны все ответы. Проверьте порядок: сначала все ответы → потом сохранение → потом уведомление.

6

Устойчивость

Бот не падает от неожиданного ввода

Заблокировано
🔒 Сначала выполните Шаг 5

Реальные пользователи присылают то, чего вы не ожидали — стикеры, голосовые, пустые сообщения. Бот должен пережить это спокойно.

step_6.txt
Шаг 6. Проверь и укрепи устойчивость:
- catch-all callback_unknown должен быть последним обработчиком
  в диспетчере
- event.answer() оборачивай в try/except на случай
  error.edit.invalid.message
- guard на event.message.sender is None перед любым обращением
  к user_id
- протестируй: отправь боту пустое сообщение, только эмодзи,
  очень длинный текст — бот не должен падать или зависать

✔ Что вы получите

Отправили боту 3-4 «странных» сообщения подряд — он не упал, ответил разумно или промолчал без ошибки в консоли.

⚠ Возможные проблемы

Бот падает от голосового или стикера

Добавьте обработку неизвестных типов сообщений — хотя бы вежливый ответ «не понял, выберите из меню».

В консоли ошибка error.edit.invalid.message

Оберните event.answer() в try/except, как указано в промте — это ожидаемая ситуация на старых callback.

Бот пытается ответить на сообщение из канала и падает

Проверьте, что guard на sender is None стоит перед каждым обращением к данным пользователя.

7

Сервер и автозапуск

Бот работает без вашего ноутбука, 24/7

Заблокировано
🔒 Сначала выполните Шаг 6

Финальный шаг — переезд с вашего компьютера на сервер, чтобы бот отвечал даже когда вы спите или в дороге.

step_7.txt
Шаг 7. Разверни бота как systemd-службу на сервере:
- unit-файл с Restart=on-failure
- WantedBy=multi-user.target, systemctl enable + start
- пройди healthcheck-блок: systemctl is-active, journalctl
  --since "10 min ago" | tail -100, python -c "import config"
Дай все команды одним блоком.

✔ Что вы получите

Закрыли ноутбук — бот отвечает. Специально убили процесс на сервере (kill) — systemd поднял его снова сам, без вашего участия.

⚠ Возможные проблемы

После перезагрузки сервера бот не поднялся сам

Забыли systemctl enable — сделали только start. Добавьте enable, чтобы служба переживала перезагрузку.

Служба падает сразу после старта

journalctl -u <service> покажет точную причину — вставьте лог целиком нейросети и попросите объяснить, не чинить вслепую.

После рестарта бот потерял базу

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

Готово. У вас есть рабочий бот 🎉

Он приветствует, собирает лиды, сохраняет их в базу, отдаёт чек-лист и сообщает вам о каждом новом клиенте — сам, без вашего участия. Это база, которая подходит под любую нишу.

Дальше — усложняем: запись на конкретное время, оплата, автонапоминания. Следующие уровни — в постах канала.

Смотреть новые публикации → Вайбкодинг для не программистов