Простая лид-воронка: бот здоровается, задаёт вопросы, сохраняет ответы, отдаёт чек-лист и сообщает вам о новом лиде. Проходите шаги по порядку — каждый следующий откроется после отметки о выполнении.
Подготовка · Системный промт
Вставляется один раз в начале нового чата с нейросетью
Это роль и правила работы для нейросети — задаёт формат ответов, стек технологий и специфику MAX API. Вставьте целиком в новый чат перед тем, как начнёте Шаг 1. Дальше все промты шагов вставляются в этот же чат, не открывайте новый.
Ты — 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. - Тестировать на реальных чужих пользователях вместо тестового чата.
Скелет и подключение к MAX
Бот запускается и отвечает на /start
Без базы, без опроса — проверяем только, что бот в принципе связан с MAX и может ответить.
Шаг 1. Создай минимальный бот для MAX:
- venv, установка библиотеки maxapi
- bot.py с dp.start_polling(bot)
- обработчик /start, который отправляет статичное приветствие
(напишите своё, например: «Привет! Я помогу подобрать [услугу].
Ответьте на 3 коротких вопроса»)
- config.py, который читает BOT_TOKEN из .env
- auto_requests=True обязательно
Дай план файлов, которые создашь, и команды для терминала одним
блоком.
✔ Что вы получите
Написали боту /start в MAX — пришло приветствие. Бот запущен локально на вашем компьютере, база и опросы пока не нужны.
⚠ Возможные проблемы
auto_requests=True не установлен. Попросите нейросеть проверить и включить этот параметр в коде запуска бота.
Проверьте, что BOT_TOKEN в .env указан верно и что config.py реально его читает — попросите добавить print для проверки.
Проверьте, что venv активирован перед pip install — частая причина, что библиотека ставится не туда.
Опрос (FSM)
Бот задаёт вопросы по очереди и помнит ответы
Здесь появляется логика диалога — бот должен вести пользователя по вопросам, не путая порядок и не забывая, на каком вопросе тот остановился.
Шаг 2. Добавь модуль state_manager и сценарий опроса из 3 вопросов после /start: Вопрос 1: (напишите своё, например: «Как вас зовут?») Вопрос 2: (напишите своё, например: «Какой у вас телефон для связи?») Вопрос 3: (напишите своё, например: «Какая услуга вас интересует?») Требования: - состояние пользователя хранится в памяти (state_manager), переход на следующий вопрос только после ответа на текущий - при новом /start состояние сбрасывается, не продолжай старый опрос - guard: если event.message.sender is None (сообщение из канала) — не обрабатывать как ответ пользователя
✔ Что вы получите
Прошли все 3 вопроса подряд — бот не путает порядок, ответы видно в консоли/логах после каждого шага.
⚠ Возможные проблемы
Состояние не сбрасывается. Попросите нейросеть явно обнулять state пользователя при каждом /start.
Нет проверки текущего состояния перед обработкой сообщения — добавьте guard: сначала смотрим state, потом решаем, что это за ответ.
sender оказался None. Добавьте проверку sender is None перед любым обращением к user_id.
Память (SQLite)
Ответы сохраняются в базу, а не пропадают
Без этого шага все ответы живут только в оперативной памяти — при первом же перезапуске бота они исчезают безвозвратно.
Шаг 3. Добавь SQLite (check_same_thread=False) и таблицу leads: id, user_id, name, contact, interest, created_at, status. После ответа на последний вопрос — сохраняй запись в таблицу и выводи в консоль подтверждение записи. Дай схему таблицы отдельным сообщением.
✔ Что вы получите
Прошли опрос, перезапустили бота, прошли опрос ещё раз — в базе видно обе записи. Схема таблицы сохранена отдельно.
⚠ Возможные проблемы
Хранение было в python-переменной, а не в БД. Проверьте, что INSERT реально пишется в SQLite, а не в словарь в памяти.
SQLite открыт без check_same_thread=False. Попросите проверить этот параметр в подключении к базе.
Проверьте, что после INSERT вызывается commit/save — без этого запись не фиксируется в файле базы.
Доставка чек-листа
Пользователь получает файл сразу после опроса
Это и есть ценность, ради которой человек проходил опрос — важно, чтобы файл дошёл, а если не дошёл — чтобы был запасной вариант.
Шаг 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 — частое место потери данных между шагами.
Это нормально — библиотека сама повторит попытку через пару секунд, не нужно ничего чинить.
MAX разрешает 1 FILE на сообщение. Отправляйте вложения по одному, не пачкой.
Уведомление владельцу
Вы узнаёте о новом лиде сразу, без захода в базу
Главная причина иметь такого бота — не пропустить ни одного лида. Этот шаг закрывает именно это.
Шаг 5. Сразу после сохранения лида в БД отправь сообщение админу: Bot.send_message(ADMIN_CHAT_ID, текст с именем, контактом и интересом лида). ADMIN_CHAT_ID — из .env, не хардкодить в коде. Это одно целевое сообщение одному человеку, не рассылка — массовых уведомлений здесь нет.
✔ Что вы получите
Прошли опрос сами как тестовый пользователь — вам как админу пришло сообщение с верными данными, и пришло ровно один раз.
⚠ Возможные проблемы
Проверьте ADMIN_CHAT_ID в .env — это должен быть именно ID чата, а не username.
Отправка вызывается в двух местах кода. Попросите нейросеть найти дубль вызова функции.
Уведомление отправляется раньше, чем собраны все ответы. Проверьте порядок: сначала все ответы → потом сохранение → потом уведомление.
Устойчивость
Бот не падает от неожиданного ввода
Реальные пользователи присылают то, чего вы не ожидали — стикеры, голосовые, пустые сообщения. Бот должен пережить это спокойно.
Шаг 6. Проверь и укрепи устойчивость: - catch-all callback_unknown должен быть последним обработчиком в диспетчере - event.answer() оборачивай в try/except на случай error.edit.invalid.message - guard на event.message.sender is None перед любым обращением к user_id - протестируй: отправь боту пустое сообщение, только эмодзи, очень длинный текст — бот не должен падать или зависать
✔ Что вы получите
Отправили боту 3-4 «странных» сообщения подряд — он не упал, ответил разумно или промолчал без ошибки в консоли.
⚠ Возможные проблемы
Добавьте обработку неизвестных типов сообщений — хотя бы вежливый ответ «не понял, выберите из меню».
Оберните event.answer() в try/except, как указано в промте — это ожидаемая ситуация на старых callback.
Проверьте, что guard на sender is None стоит перед каждым обращением к данным пользователя.
Сервер и автозапуск
Бот работает без вашего ноутбука, 24/7
Финальный шаг — переезд с вашего компьютера на сервер, чтобы бот отвечал даже когда вы спите или в дороге.
Шаг 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-файлу абсолютный, а не временный — иначе при каждом рестарте создаётся новая пустая база.
Он приветствует, собирает лиды, сохраняет их в базу, отдаёт чек-лист и сообщает вам о каждом новом клиенте — сам, без вашего участия. Это база, которая подходит под любую нишу.
Дальше — усложняем: запись на конкретное время, оплата, автонапоминания. Следующие уровни — в постах канала.
Смотреть новые публикации → Вайбкодинг для не программистов