Разработка чат-ботов для мессенджера MAX: полное руководство

Telegram в России работает всё хуже, а бизнес годами строил автоматизацию на его ботах. Рассказываем, как быстро перенести логику в MAX — российский мессенджер от VK с полноценным Bot API.
Содержание:
- Почему MAX и почему срочно?
- Шаг 1: Регистрация организации в MAX Business
- Шаг 2: Создание бота
- Шаг 3: Технологический стек
- Шаг 4: Базовая структура бота
- Шаг 5: Работа с кнопками
- Шаг 6: Обработка файлов и медиа
- Шаг 7: Хранение данных пользователей
- Шаг 8: Регистрация команд бота
- Шаг 9: Состояния и диалоги
- Шаг 10: Рассылка сообщений
- Шаг 11: Деплой в Docker
- Шаг 12: Мониторинг и логирование
- Примеры применения ботов MAX в бизнесе
- Полный код готового бота
- Заключение
Почему MAX и почему срочно?

Давайте честно: Telegram в России переживает не лучшие времена. Периодические замедления, проблемы с доставкой сообщений, нестабильная работа ботов — всё это уже реальность для многих пользователей. И ситуация, судя по всему, будет только усугубляться.
Проблема: бизнес завязан на Telegram-ботах
За последние годы компании построили на Telegram целые экосистемы:
Боты записи клиентов — салоны красоты, клиники, автосервисы
HR-боты — онбординг, заявки на отпуск, справки
Техподдержка — приём заявок, автоответы, эскалация
Уведомления — статусы заказов, напоминания, рассылки
Внутренние инструменты — отчёты, согласования, документооборот
Когда Telegram «лежит» или тормозит — бизнес теряет клиентов и деньги. Записи срываются, заявки не доходят, сотрудники не получают важные уведомления.
Решение: MAX как надёжная альтернатива
MAX (бывший VK Teams) — российский корпоративный мессенджер от VK. Ключевые преимущества:
Работает стабильно — серверы в России, нет проблем с блокировками
Полноценный Bot API — всё то же самое, что в Telegram, только надёжнее
Данные в РФ — соответствие 152-ФЗ, никаких рисков с трансграничной передачей
Бесплатный старт — нет платы за базовый функционал
Знакомый интерфейс — пользователи VK освоятся за минуты
По нашим наблюдениям, запросы на экстренную миграцию ботов из Telegram в MAX выросли в 4-5 раз за последние месяцы. Компании понимают: нужен запасной вариант, пока Telegram-боты ещё работают. Те, кто откладывает — рискуют остаться без автоматизации в самый неподходящий момент.
Хорошая новость
Если у вас уже есть Telegram-бот на Python, перенос в MAX — дело нескольких дней. Архитектура похожа, библиотека maxapi напоминает aiogram, большую часть логики можно переиспользовать.
Шаг 1: Регистрация организации в MAX Business
Для создания бота вам потребуется бизнес-аккаунт MAX. Вот как его получить:
1.1 Переход в MAX Business
Откройте business.max.ru и нажмите «Начать использовать». Вам предложат создать рабочее пространство для компании.
1.2 Заполнение данных организации
Поле | Описание | Пример |
|---|---|---|
Название | Юридическое или бренд-имя | ООО «Ромашка» |
Домен | Уникальный адрес пространства | romashka.max.ru |
Сфера деятельности | Выбор из списка | IT / Услуги |
Размер | Количество сотрудников | 10-50 человек |
1.3 Подтверждение и настройка
После создания организации вы получите доступ к панели администратора, где можно:
Приглашать сотрудников
Управлять правами доступа
Создавать ботов и интеграции
Настраивать каналы и чаты
Шаг 2: Создание бота
2.1 Переход в раздел ботов
В панели администратора перейдите в раздел «Боты и интеграции» → «Создать бота». Здесь будет список всех ваших ботов и кнопка создания нового.
2.2 Настройка бота
При создании бота укажите:
Параметр | Описание |
|---|---|
Имя бота | Отображается в списке контактов (например, «HR-помощник») |
Username | Уникальный идентификатор (@hr_helper_bot) |
Описание | Краткое описание функционала |
Аватар | Изображение 512×512 px |
2.3 Получение токена
После создания бота вы получите токен доступа — длинную строку, которая используется для авторизации API-запросов:
Токен бота: a1b2c3ddsfs4e57h8i9j0k1l2m3n4o5p6q7r8s9tВажно: Храните токен в безопасном месте. Никогда не коммитьте его в репозиторий. Используйте переменные окружения.
Шаг 3: Технологический стек
Выбор языка и библиотеки
Для разработки ботов MAX мы рекомендуем Python с библиотекой maxapi:
Технология | Версия | Назначение |
|---|---|---|
Python | 3.8+ | Основной язык |
maxapi | 0.9.13+ | Библиотека для MAX Bot API |
asyncio | встроен | Асинхронное выполнение |
aiohttp | 3.8+ | HTTP-клиент для внешних запросов |
aiofiles | 23.0+ | Асинхронная работа с файлами |
Установка зависимостей
# Создание виртуального окружения
python -m venv venv
source venv/bin/activate # Linux/macOS
# или
venv\Scripts\activate # Windows
# Установка библиотек
pip install maxapi python-dotenv aiohttp aiofilesФайл requirements.txt
maxapi>=0.9.13
python-dotenv>=1.0.0
aiohttp>=3.8.0
aiofiles>=23.0.0Шаг 4: Базовая структура бота
4.1 Минимальный работающий бот
Создайте файл bot.py:
import os
import asyncio
import logging
from dotenv import load_dotenv
# Загружаем переменные окружения
load_dotenv()
# Настройка логирования
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
# Импортируем maxapi
from maxapi import Bot, Dispatcher
from maxapi.types import MessageCreated, BotStarted, Command
# Получаем токен из окружения
BOT_TOKEN = os.getenv("MAX_BOT_TOKEN")
if not BOT_TOKEN:
logger.error("Токен бота не найден! Установите MAX_BOT_TOKEN в .env")
exit(1)
# Создаём экземпляры бота и диспетчера
bot = Bot(token=BOT_TOKEN)
dp = Dispatcher()
# Обработчик нажатия кнопки "Начать" (первый запуск)
@dp.bot_started()
async def handle_bot_started(event: BotStarted):
"""Вызывается когда пользователь нажимает 'Начать'"""
user = event.user
name = getattr(user, 'first_name', None) or 'друг'
logger.info(f"Новый пользователь: {name} (ID: {user.user_id})")
await bot.send_message(
chat_id=event.chat_id,
text=f"👋 Привет, {name}!\n\n"
"Я ваш помощник. Чем могу помочь?\n\n"
"Используйте /help для справки."
)
# Обработчик команды /start
@dp.message_created(Command('start'))
async def cmd_start(event: MessageCreated):
"""Обработчик команды /start"""
user = event.message.sender
name = getattr(user, 'first_name', None) or 'друг'
await event.message.answer(
f"👋 Привет, {name}!\n\n"
"Добро пожаловать! Я готов помочь."
)
# Обработчик команды /help
@dp.message_created(Command('help'))
async def cmd_help(event: MessageCreated):
"""Обработчик команды /help"""
help_text = """
📋 **Доступные команды:**
• /start — Запустить бота
• /help — Показать эту справку
• /info — Информация о боте
Просто напишите мне сообщение, и я отвечу!
"""
await event.message.answer(help_text)
# Главная функция
async def main():
logger.info("Бот запускается...")
# Удаляем webhook для режима polling
try:
await bot.delete_webhook()
except Exception as e:
logger.warning(f"Ошибка удаления webhook: {e}")
logger.info("Бот запущен и ожидает сообщений")
await dp.start_polling(bot)
if __name__ == "__main__":
asyncio.run(main())4.2 Файл конфигурации .env
# Токен бота MAX (получить в business.max.ru)
MAX_BOT_TOKEN=ваш_токен_здесь
# Опционально: режим отладки
DEBUG=true4.3 Запуск бота
python bot.pyЕсли всё настроено правильно, вы увидите:
2026-02-11 10:30:00 - __main__ - INFO - Бот запускается...
2026-02-11 10:30:03 - __main__ - INFO - Бот запущен и ожидает сообщенийШаг 5: Работа с кнопками
Кнопки — мощный инструмент для создания интерактивных ботов. MAX поддерживает inline-кнопки, которые отображаются под сообщением.
5.1 Типы кнопок
from maxapi.types import CallbackButton, ButtonsPayload, Attachment
from maxapi.enums.intent import IntentIntent | Описание | Внешний вид |
|---|---|---|
| Обычная кнопка | Серая |
| Подтверждение | Зелёная |
| Отмена/Удаление | Красная |
5.2 Создание кнопок
from maxapi.types import CallbackButton, ButtonsPayload, Attachment
from maxapi.enums.intent import Intent
def create_main_menu():
"""Создаёт главное меню с кнопками"""
# Создаём кнопки
btn_help = CallbackButton(
text="📋 Помощь",
payload="cmd_help",
intent=Intent.DEFAULT
)
btn_settings = CallbackButton(
text="⚙️ Настройки",
payload="cmd_settings",
intent=Intent.DEFAULT
)
btn_support = CallbackButton(
text="💬 Поддержка",
payload="cmd_support",
intent=Intent.POSITIVE
)
# Группируем кнопки в ряды
# Каждый вложенный список — отдельный ряд
buttons_payload = ButtonsPayload(
buttons=[
[btn_help, btn_settings], # Первый ряд: 2 кнопки
[btn_support] # Второй ряд: 1 кнопка
]
)
# Создаём attachment для отправки
return Attachment(type="inline_keyboard", payload=buttons_payload)5.3 Отправка сообщения с кнопками
@dp.message_created(Command('menu'))
async def cmd_menu(event: MessageCreated):
"""Показать главное меню"""
menu_buttons = create_main_menu()
await bot.send_message(
chat_id=event.message.recipient.chat_id,
text="🏠 **Главное меню**\n\nВыберите действие:",
attachments=[menu_buttons]
)5.4 Обработка нажатий на кнопки
from maxapi.types import MessageCallback
from maxapi import F
# Обработчик конкретной кнопки по payload
@dp.message_callback(F.callback.payload == "cmd_help")
async def callback_help(event: MessageCallback):
"""Обработка нажатия кнопки 'Помощь'"""
# Отправляем уведомление (toast)
await event.answer(notification="📋 Открываю справку...")
# Отправляем сообщение
chat_id = event.message.recipient.chat_id
await bot.send_message(
chat_id=chat_id,
text="📋 **Справка**\n\n"
"Здесь будет подробная информация о боте."
)
@dp.message_callback(F.callback.payload == "cmd_settings")
async def callback_settings(event: MessageCallback):
"""Обработка нажатия кнопки 'Настройки'"""
await event.answer(notification="⚙️ Настройки")
# Можно редактировать исходное сообщение
await event.message.edit(
text="⚙️ **Настройки**\n\n"
"Выберите параметр для изменения:",
attachments=[create_settings_menu()]
)
@dp.message_callback(F.callback.payload == "cmd_support")
async def callback_support(event: MessageCallback):
"""Обработка нажатия кнопки 'Поддержка'"""
user_id = event.callback.user.user_id
await event.answer(notification="💬 Режим поддержки активирован")
# Сохраняем состояние пользователя
user_states[user_id] = "support_mode"
await bot.send_message(
chat_id=event.message.recipient.chat_id,
text="💬 **Техподдержка**\n\n"
"Опишите вашу проблему, и мы ответим в ближайшее время."
)5.5 Как это работает
Логика простая: каждая кнопка имеет payload — строку-идентификатор. Когда пользователь нажимает кнопку, бот получает callback с этим payload и вызывает соответствующий обработчик через декоратор @dp.message_callback(F.callback.payload == "...").
Шаг 6: Обработка файлов и медиа
Бот может принимать файлы от пользователей и отправлять файлы в ответ.
6.1 Приём файлов
from maxapi import F
import aiohttp
import aiofiles
import os
# Поддерживаемые форматы
SUPPORTED_FORMATS = ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'txt', 'csv']
MAX_FILE_SIZE = 50 * 1024 * 1024 # 50 MB
@dp.message_created(F.message.body.attachments)
async def handle_file(event: MessageCreated):
"""Обработчик входящих файлов"""
message = event.message
user = message.sender
chat_id = message.recipient.chat_id
attachments = message.body.attachments
if not attachments:
return
for attachment in attachments:
att_type = getattr(attachment, 'type', None)
# Обрабатываем только файлы
if att_type not in ['file', 'audio', 'video']:
continue
# Получаем информацию о файле
file_name = getattr(attachment, 'filename', 'unknown')
file_size = getattr(attachment, 'size', 0)
file_url = getattr(attachment, 'url', None)
# Проверяем формат
if '.' in file_name:
ext = file_name.rsplit('.', 1)[-1].lower()
if ext not in SUPPORTED_FORMATS:
await message.answer(
f"❌ Формат .{ext} не поддерживается.\n"
f"Поддерживаемые форматы: {', '.join(SUPPORTED_FORMATS)}"
)
continue
# Проверяем размер
if file_size > MAX_FILE_SIZE:
await message.answer(
f"❌ Файл слишком большой ({file_size / (1024*1024):.1f} МБ).\n"
f"Максимум: {MAX_FILE_SIZE / (1024*1024):.0f} МБ"
)
continue
# Скачиваем файл
if file_url:
await message.answer(f"📥 Получен файл: {file_name}\nОбрабатываю...")
# Запускаем обработку
await process_file(file_url, file_name, user.user_id, chat_id)
async def process_file(file_url: str, file_name: str, user_id: int, chat_id: int):
"""Скачивание и обработка файла"""
temp_dir = 'temp'
os.makedirs(temp_dir, exist_ok=True)
temp_path = os.path.join(temp_dir, f"{user_id}_{file_name}")
try:
# Скачиваем файл
async with aiohttp.ClientSession() as session:
async with session.get(file_url) as resp:
if resp.status != 200:
raise Exception(f"Ошибка загрузки: HTTP {resp.status}")
async with aiofiles.open(temp_path, 'wb') as f:
async for chunk in resp.content.iter_chunked(8192):
await f.write(chunk)
# Здесь ваша логика обработки файла
# Например: парсинг, анализ, конвертация
await bot.send_message(
chat_id=chat_id,
text=f"✅ Файл {file_name} успешно обработан!"
)
except Exception as e:
logger.error(f"Ошибка обработки файла: {e}")
await bot.send_message(
chat_id=chat_id,
text=f"❌ Ошибка обработки: {str(e)}"
)
finally:
# Удаляем временный файл
if os.path.exists(temp_path):
os.remove(temp_path)6.2 Отправка файлов
from maxapi.types import InputMedia
async def send_file_to_user(chat_id: int, file_path: str, caption: str):
"""Отправка файла пользователю"""
try:
await bot.send_message(
chat_id=chat_id,
text=caption,
attachments=[InputMedia(path=file_path)]
)
return True
except Exception as e:
logger.error(f"Ошибка отправки файла: {e}")
return False
# Отправка с retry при ошибке "attachment.not.ready"
async def send_file_with_retry(chat_id: int, file_path: str,
text: str, max_retries: int = 5):
"""Отправка файла с повторными попытками"""
for attempt in range(max_retries):
try:
await bot.send_message(
chat_id=chat_id,
text=text,
attachments=[InputMedia(path=file_path)]
)
return True
except Exception as e:
if 'attachment.not.ready' in str(e):
if attempt < max_retries - 1:
logger.warning(f"Файл не готов, попытка {attempt + 1}/{max_retries}")
await asyncio.sleep(3.0)
continue
logger.error(f"Ошибка отправки: {e}")
return False
return FalseШаг 7: Хранение данных пользователей
Для персонализации бота нужно хранить информацию о пользователях.
7.1 Простое JSON-хранилище
import json
import time
from typing import Dict, Optional
USERS_FILE = 'users.json'
def load_users() -> Dict:
"""Загрузка данных пользователей"""
try:
if os.path.exists(USERS_FILE):
with open(USERS_FILE, 'r', encoding='utf-8') as f:
return json.load(f)
return {}
except Exception as e:
logger.error(f"Ошибка загрузки пользователей: {e}")
return {}
def save_users(users: Dict):
"""Сохранение данных пользователей"""
try:
with open(USERS_FILE, 'w', encoding='utf-8') as f:
json.dump(users, f, ensure_ascii=False, indent=2)
except Exception as e:
logger.error(f"Ошибка сохранения пользователей: {e}")
def add_user(user_id: int, chat_id: int,
username: Optional[str] = None,
name: Optional[str] = None) -> bool:
"""Добавление или обновление пользователя"""
users = load_users()
user_id_str = str(user_id)
is_new = user_id_str not in users
users[user_id_str] = {
'chat_id': chat_id,
'username': username,
'name': name,
'created_at': users.get(user_id_str, {}).get(
'created_at',
time.strftime('%Y-%m-%d %H:%M:%S')
),
'last_activity': time.strftime('%Y-%m-%d %H:%M:%S')
}
save_users(users)
if is_new:
logger.info(f"Новый пользователь: {name} (ID: {user_id})")
return is_new
def get_user(user_id: int) -> Optional[Dict]:
"""Получение данных пользователя"""
users = load_users()
return users.get(str(user_id))
def get_all_users() -> Dict:
"""Получение всех пользователей"""
return load_users()7.2 Использование в обработчиках
@dp.bot_started()
async def handle_bot_started(event: BotStarted):
user = event.user
username = getattr(user, 'username', None)
name = getattr(user, 'first_name', None) or getattr(user, 'full_name', None)
# Сохраняем пользователя
is_new = add_user(
user_id=user.user_id,
chat_id=event.chat_id,
username=username,
name=name
)
if is_new:
# Приветствие для нового пользователя
await bot.send_message(
chat_id=event.chat_id,
text=f"🎉 Добро пожаловать, {name}!\n\n"
"Вы первый раз у нас. Давайте познакомимся!"
)
else:
# Приветствие для вернувшегося
await bot.send_message(
chat_id=event.chat_id,
text=f"👋 С возвращением, {name}!"
)Шаг 8: Регистрация команд бота
MAX позволяет зарегистрировать команды, которые будут отображаться в меню бота.
from maxapi.types import BotCommand
async def setup_bot_commands():
"""Регистрация команд в меню бота"""
try:
await bot.set_my_commands(
BotCommand(name="start", description="Запустить бота"),
BotCommand(name="help", description="Показать справку"),
BotCommand(name="menu", description="Главное меню"),
BotCommand(name="settings", description="Настройки"),
BotCommand(name="support", description="Техподдержка"),
)
logger.info("Команды бота зарегистрированы")
except Exception as e:
logger.warning(f"Ошибка регистрации команд: {e}")
async def main():
logger.info("Бот запускается...")
# Регистрируем команды
await setup_bot_commands()
# Удаляем webhook
await bot.delete_webhook()
# Запускаем polling
logger.info("Бот запущен")
await dp.start_polling(bot)Шаг 9: Состояния и диалоги
Для сложных сценариев (анкеты, многошаговые формы) нужно отслеживать состояние диалога.
9.1 Простой менеджер состояний
from enum import Enum
from typing import Dict, Any
class UserState(Enum):
"""Возможные состояния пользователя"""
IDLE = "idle"
AWAITING_NAME = "awaiting_name"
AWAITING_EMAIL = "awaiting_email"
AWAITING_MESSAGE = "awaiting_message"
SUPPORT_MODE = "support_mode"
# Хранилище состояний (в production используйте Redis)
user_states: Dict[int, UserState] = {}
user_data: Dict[int, Dict[str, Any]] = {}
def get_state(user_id: int) -> UserState:
"""Получить состояние пользователя"""
return user_states.get(user_id, UserState.IDLE)
def set_state(user_id: int, state: UserState):
"""Установить состояние пользователя"""
user_states[user_id] = state
def get_data(user_id: int) -> Dict[str, Any]:
"""Получить данные пользователя"""
return user_data.get(user_id, {})
def set_data(user_id: int, key: str, value: Any):
"""Сохранить данные пользователя"""
if user_id not in user_data:
user_data[user_id] = {}
user_data[user_id][key] = value
def clear_state(user_id: int):
"""Очистить состояние и данные"""
user_states.pop(user_id, None)
user_data.pop(user_id, None)9.2 Пример многошагового диалога
# Начало анкеты
@dp.message_created(Command('register'))
async def cmd_register(event: MessageCreated):
user_id = event.message.sender.user_id
set_state(user_id, UserState.AWAITING_NAME)
await event.message.answer(
"📝 **Регистрация**\n\n"
"Шаг 1/3: Введите ваше имя:"
)
# Обработка текстовых сообщений с учётом состояния
@dp.message_created(F.message.body.text)
async def handle_text(event: MessageCreated):
text = event.message.body.text
# Игнорируем команды
if text and text.startswith('/'):
return
user_id = event.message.sender.user_id
state = get_state(user_id)
# Обработка по состояниям
if state == UserState.AWAITING_NAME:
await handle_name_input(event, text)
elif state == UserState.AWAITING_EMAIL:
await handle_email_input(event, text)
elif state == UserState.AWAITING_MESSAGE:
await handle_message_input(event, text)
elif state == UserState.SUPPORT_MODE:
await handle_support_message(event, text)
else:
# Состояние IDLE - обычный ответ
await event.message.answer(
"Не понял вас. Используйте /help для справки."
)
async def handle_name_input(event: MessageCreated, name: str):
user_id = event.message.sender.user_id
# Валидация
if len(name) < 2:
await event.message.answer("❌ Имя слишком короткое. Попробуйте ещё раз:")
return
# Сохраняем и переходим к следующему шагу
set_data(user_id, 'name', name)
set_state(user_id, UserState.AWAITING_EMAIL)
await event.message.answer(
f"✅ Отлично, {name}!\n\n"
"Шаг 2/3: Введите ваш email:"
)
async def handle_email_input(event: MessageCreated, email: str):
user_id = event.message.sender.user_id
# Простая валидация email
if '@' not in email or '.' not in email:
await event.message.answer("❌ Некорректный email. Попробуйте ещё раз:")
return
set_data(user_id, 'email', email)
set_state(user_id, UserState.AWAITING_MESSAGE)
await event.message.answer(
"✅ Email сохранён!\n\n"
"Шаг 3/3: Опишите вашу задачу:"
)
async def handle_message_input(event: MessageCreated, message: str):
user_id = event.message.sender.user_id
data = get_data(user_id)
# Завершаем регистрацию
name = data.get('name', 'Не указано')
email = data.get('email', 'Не указано')
# Здесь можно сохранить в БД или отправить уведомление
logger.info(f"Новая заявка: {name}, {email}, {message[:50]}...")
# Очищаем состояние
clear_state(user_id)
await event.message.answer(
"🎉 **Регистрация завершена!**\n\n"
f"📋 Ваши данные:\n"
f"• Имя: {name}\n"
f"• Email: {email}\n"
f"• Сообщение: {message[:100]}...\n\n"
"Мы свяжемся с вами в ближайшее время!"
)Шаг 10: Рассылка сообщений
Функционал массовой рассылки полезен для уведомлений и анонсов.
import asyncio
ADMIN_ID = 12345678 # ID администратора
@dp.message_created(Command('broadcast'))
async def cmd_broadcast(event: MessageCreated):
"""Начало рассылки (только для админа)"""
user_id = event.message.sender.user_id
if user_id != ADMIN_ID:
await event.message.answer("⛔ Команда доступна только администратору.")
return
users = get_all_users()
if not users:
await event.message.answer("⚠️ Нет пользователей для рассылки.")
return
set_state(user_id, UserState.AWAITING_MESSAGE)
set_data(user_id, 'broadcast_mode', True)
await event.message.answer(
f"📢 **Режим рассылки**\n\n"
f"Пользователей в базе: {len(users)}\n\n"
"Отправьте текст для рассылки или /cancel для отмены:"
)
async def broadcast_message(text: str, admin_chat_id: int):
"""Выполнение рассылки"""
users = get_all_users()
success = 0
failed = 0
await bot.send_message(
chat_id=admin_chat_id,
text=f"📤 Начинаю рассылку на {len(users)} пользователей..."
)
for user_id_str, user_data in users.items():
try:
chat_id = user_data.get('chat_id')
if not chat_id:
continue
await bot.send_message(chat_id=chat_id, text=text)
success += 1
# Задержка чтобы не превысить лимиты
await asyncio.sleep(0.1)
except Exception as e:
logger.error(f"Ошибка рассылки пользователю {user_id_str}: {e}")
failed += 1
await bot.send_message(
chat_id=admin_chat_id,
text=f"✅ **Рассылка завершена!**\n\n"
f"📊 Статистика:\n"
f"• Успешно: {success}\n"
f"• Ошибок: {failed}\n"
f"• Всего: {len(users)}"
)Шаг 11: Деплой в Docker
11.1 Dockerfile
FROM python:3.11-slim
WORKDIR /app
# Копируем зависимости
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Копируем код
COPY . .
# Запуск
CMD ["python", "bot.py"]11.2 docker-compose.yml
version: '3.8'
services:
max-bot:
build: .
container_name: max-bot
restart: unless-stopped
env_file:
- .env
volumes:
- ./data:/app/data # Для персистентных данных
- ./logs:/app/logs # Для логов
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"11.3 Запуск
# Сборка и запуск
docker-compose up -d --build
# Просмотр логов
docker-compose logs -f max-bot
# Остановка
docker-compose downШаг 12: Мониторинг и логирование
12.1 Расширенное логирование
import logging
from logging.handlers import RotatingFileHandler
def setup_logging():
"""Настройка логирования с ротацией файлов"""
logger = logging.getLogger()
logger.setLevel(logging.INFO)
# Формат логов
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
# Консольный вывод
console_handler = logging.StreamHandler()
console_handler.setFormatter(formatter)
logger.addHandler(console_handler)
# Файловый вывод с ротацией
file_handler = RotatingFileHandler(
'logs/bot.log',
maxBytes=10*1024*1024, # 10 MB
backupCount=5,
encoding='utf-8'
)
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)
return logger12.2 Статистика использования
import json
from datetime import datetime
STATS_FILE = 'data/statistics.json'
def load_stats():
try:
with open(STATS_FILE, 'r') as f:
return json.load(f)
except:
return {
'total_users': 0,
'total_messages': 0,
'commands': {},
'daily_stats': {}
}
def save_stats(stats):
with open(STATS_FILE, 'w') as f:
json.dump(stats, f, indent=2)
def track_event(event_type: str, user_id: int = None):
"""Отслеживание события"""
stats = load_stats()
today = datetime.now().strftime('%Y-%m-%d')
# Общий счётчик
stats['total_messages'] = stats.get('total_messages', 0) + 1
# По командам
if event_type.startswith('/'):
cmd = event_type[1:]
stats['commands'][cmd] = stats['commands'].get(cmd, 0) + 1
# По дням
if today not in stats['daily_stats']:
stats['daily_stats'][today] = {'messages': 0, 'users': set()}
stats['daily_stats'][today]['messages'] += 1
save_stats(stats)Примеры применения ботов MAX в бизнесе

HR-бот
Функции:
• Ответы на типовые вопросы (отпуск, больничный, справки)
• Приём заявок на документы
• Онбординг новых сотрудников
• Напоминания о важных датах
Эффект: HR-отдел экономит до 40% времени на рутинных запросах
IT-бот техподдержки
Функции:
• Приём заявок на техподдержку
• Автоматические инструкции по типовым проблемам
• Статус выполнения заявки
• Эскалация сложных случаев
Эффект: 60% обращений закрываются автоматически
Бот для отдела продаж
Функции:
• Быстрый доступ к информации о продуктах
• Расчёт стоимости и скидок
• Генерация коммерческих предложений
• Интеграция с CRM
Эффект: Менеджеры отвечают клиентам в 3 раза быстрее
Полный код готового бота
Соберём всё вместе в production-ready решение:
"""
MAX Bot — полнофункциональный бот для мессенджера MAX
"""
import os
import json
import asyncio
import logging
from datetime import datetime
from enum import Enum
from typing import Dict, Optional, Any
from dotenv import load_dotenv
from maxapi import Bot, Dispatcher, F
from maxapi.types import (
MessageCreated, BotStarted, MessageCallback, Command,
CallbackButton, ButtonsPayload, Attachment, BotCommand
)
from maxapi.enums.intent import Intent
from maxapi.enums.parse_mode import ParseMode
# Загружаем конфигурацию
load_dotenv()
# Логирование
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler("bot.log"),
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__)
# Конфигурация
BOT_TOKEN = os.getenv("MAX_BOT_TOKEN")
ADMIN_ID = int(os.getenv("ADMIN_ID", "0"))
USERS_FILE = 'data/users.json'
if not BOT_TOKEN:
logger.error("MAX_BOT_TOKEN не найден в .env")
exit(1)
# Инициализация
bot = Bot(token=BOT_TOKEN)
dp = Dispatcher()
# ==================== СОСТОЯНИЯ ====================
class UserState(Enum):
IDLE = "idle"
SUPPORT_MODE = "support"
BROADCAST_MODE = "broadcast"
user_states: Dict[int, UserState] = {}
# ==================== ХРАНИЛИЩЕ ====================
def load_users() -> Dict:
try:
os.makedirs('data', exist_ok=True)
if os.path.exists(USERS_FILE):
with open(USERS_FILE, 'r', encoding='utf-8') as f:
return json.load(f)
except Exception as e:
logger.error(f"Ошибка загрузки: {e}")
return {}
def save_users(users: Dict):
try:
os.makedirs('data', exist_ok=True)
with open(USERS_FILE, 'w', encoding='utf-8') as f:
json.dump(users, f, ensure_ascii=False, indent=2)
except Exception as e:
logger.error(f"Ошибка сохранения: {e}")
def add_user(user_id: int, chat_id: int, username: str = None, name: str = None):
users = load_users()
uid = str(user_id)
is_new = uid not in users
users[uid] = {
'chat_id': chat_id,
'username': username,
'name': name,
'created_at': users.get(uid, {}).get('created_at', datetime.now().isoformat()),
'last_activity': datetime.now().isoformat()
}
save_users(users)
return is_new
# ==================== КНОПКИ ====================
def create_main_menu() -> Attachment:
buttons = ButtonsPayload(buttons=[
[
CallbackButton(text="📋 Помощь", payload="help", intent=Intent.DEFAULT),
CallbackButton(text="ℹ️ О боте", payload="about", intent=Intent.DEFAULT)
],
[
CallbackButton(text="💬 Поддержка", payload="support", intent=Intent.POSITIVE)
]
])
return Attachment(type="inline_keyboard", payload=buttons)
# ==================== ОБРАБОТЧИКИ ====================
@dp.bot_started()
async def handle_started(event: BotStarted):
user = event.user
name = getattr(user, 'first_name', 'друг')
username = getattr(user, 'username', None)
is_new = add_user(user.user_id, event.chat_id, username, name)
logger.info(f"{'Новый' if is_new else 'Вернувшийся'} пользователь: {name}")
await bot.send_message(
chat_id=event.chat_id,
text=f"👋 Привет, {name}!\n\nДобро пожаловать! Чем могу помочь?",
attachments=[create_main_menu()]
)
@dp.message_created(Command('start'))
async def cmd_start(event: MessageCreated):
user = event.message.sender
name = getattr(user, 'first_name', 'друг')
await bot.send_message(
chat_id=event.message.recipient.chat_id,
text=f"👋 Привет, {name}!\n\nВыберите действие:",
attachments=[create_main_menu()]
)
@dp.message_created(Command('help'))
async def cmd_help(event: MessageCreated):
await event.message.answer(
"📋 **Справка**\n\n"
"Доступные команды:\n"
"• /start — Главное меню\n"
"• /help — Эта справка\n"
"• /support — Связаться с поддержкой\n\n"
"Просто напишите ваш вопрос!",
parse_mode=ParseMode.MARKDOWN
)
@dp.message_callback(F.callback.payload == "help")
async def callback_help(event: MessageCallback):
await event.answer(notification="📋 Справка")
await bot.send_message(
chat_id=event.message.recipient.chat_id,
text="📋 **Справка**\n\nИспользуйте меню для навигации.",
parse_mode=ParseMode.MARKDOWN
)
@dp.message_callback(F.callback.payload == "support")
async def callback_support(event: MessageCallback):
user_id = event.callback.user.user_id
user_states[user_id] = UserState.SUPPORT_MODE
await event.answer(notification="💬 Режим поддержки")
exit_btn = CallbackButton(text="🔙 Выйти", payload="exit_support", intent=Intent.DEFAULT)
buttons = ButtonsPayload(buttons=[[exit_btn]])
await bot.send_message(
chat_id=event.message.recipient.chat_id,
text="💬 **Поддержка**\n\nОпишите вашу проблему:",
parse_mode=ParseMode.MARKDOWN,
attachments=[Attachment(type="inline_keyboard", payload=buttons)]
)
@dp.message_callback(F.callback.payload == "exit_support")
async def callback_exit_support(event: MessageCallback):
user_id = event.callback.user.user_id
user_states.pop(user_id, None)
await event.answer(notification="✅ Вы вышли из поддержки")
await event.message.edit(text="✅ Вы вышли из режима поддержки.", attachments=[])
@dp.message_callback(F.callback.payload == "about")
async def callback_about(event: MessageCallback):
await event.answer(notification="ℹ️ О боте")
await bot.send_message(
chat_id=event.message.recipient.chat_id,
text="ℹ️ **О боте**\n\n"
"Версия: 1.0.0\n"
"Создан для автоматизации рабочих процессов.\n\n"
"Разработка: mediaten.ru",
parse_mode=ParseMode.MARKDOWN
)
@dp.message_created(F.message.body.text)
async def handle_text(event: MessageCreated):
text = event.message.body.text
if text and text.startswith('/'):
return
user_id = event.message.sender.user_id
state = user_states.get(user_id, UserState.IDLE)
if state == UserState.SUPPORT_MODE:
# Пересылаем админу
name = getattr(event.message.sender, 'first_name', 'Пользователь')
logger.info(f"Сообщение в поддержку от {name}: {text[:50]}...")
await event.message.answer(
"✅ Сообщение отправлено!\n"
"Мы ответим в ближайшее время."
)
else:
await event.message.answer(
"Не понял вас. Используйте /help для справки."
)
# ==================== ЗАПУСК ====================
async def main():
logger.info("Запуск бота...")
# Регистрация команд
await bot.set_my_commands(
BotCommand(name="start", description="Главное меню"),
BotCommand(name="help", description="Справка"),
BotCommand(name="support", description="Поддержка"),
)
await bot.delete_webhook()
logger.info("Бот запущен")
await dp.start_polling(bot)
if __name__ == "__main__":
asyncio.run(main())Заключение
Разработка ботов для MAX — это доступный способ автоматизировать бизнес-процессы в российском корпоративном мессенджере. Ключевые преимущества:
Простой API — библиотека maxapi делает разработку интуитивной
Асинхронность — высокая производительность на asyncio
Гибкость — inline-кнопки, файлы, состояния, рассылки
Безопасность — данные остаются на территории РФ
Начните с простого бота и постепенно добавляйте функционал по мере необходимости. Главное — понять потребности пользователей и автоматизировать рутинные операции.
Нужна помощь с разработкой бота?
MediaTen — разрабатываем чат-боты для мессенджера MAX под ключ. От простых FAQ-ботов до сложных интеграций с CRM и внутренними системами.
Что мы делаем:
Проектирование архитектуры бота под ваши задачи
Разработка и тестирование
Интеграция с существующими системами (1С, CRM, ERP)
Деплой и поддержка
Сроки: от 2 недель для типового бота, от 1 месяца для сложных интеграций.
👉 Заказать разработку чат-бота для MAX
Бесплатная консультация — расскажем, какой бот нужен именно вам и сколько это будет стоить.
Похожие статьи
Готовы применить идеи из статьи?
Опишите вашу задачу — сделаем бесплатный экспресс-разбор и предложим 2–3 рабочих решения под ваш кейс.


