0%

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

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

Telegram в России работает всё хуже, а бизнес годами строил автоматизацию на его ботах. Рассказываем, как быстро перенести логику в MAX — российский мессенджер от VK с полноценным Bot API.

Содержание:

Почему 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=true

4.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 Intent

Intent

Описание

Внешний вид

Intent.DEFAULT

Обычная кнопка

Серая

Intent.POSITIVE

Подтверждение

Зелёная

Intent.NEGATIVE

Отмена/Удаление

Красная

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 logger

12.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 в бизнесе

Примеры применения ботов 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

Бесплатная консультация — расскажем, какой бот нужен именно вам и сколько это будет стоить.

Похожие статьи

IconГотовы применить идеи из статьи?

Опишите вашу задачу — сделаем бесплатный экспресс-разбор и предложим 2–3 рабочих решения под ваш кейс.

MediaTen — цифровые решенияMediaTen — креативный подход