Привет! С выходом платформы MAX у разработчиков появилось новое игровое поле. Пока комьюнити спорит о шансах на победу в гонке мессенджеров, маркетологи уже начали переливать туда трафик.
Самая типовая задача для бизнеса сейчас — бот обратной связи. В Telegram эту нишу давно занял Olgram, а вот в Max — чистый лист. Давайте вместе напишем свой аналог. Это отличный кейс, чтобы разобраться с новым API, не углубляясь в лишнюю инфраструктуру.
Стек: Почему все оказалось проще, чем кажется
Для MVP (Minimum Viable Product) мы будем использовать Node.js и официальную библиотеку @maxhub/max-bot-api.
Здесь важно отметить один момент, который сэкономит вам кучу времени. В документации Max упор делается на Webhooks. Это значит, что по классике вам нужен сервер с "белым" IP, HTTPS-сертификат и, скорее всего, танцы с ngrok или Cloudflare Tunnel для локальной разработки.
Но официальная JS-библиотека поддерживает Long Polling "из коробки". Бот сам опрашивает сервера Max. Это позволяет запустить проект на локалке без единого открытого порта. Никаких ngrok, никаких настроек серверов. Просто npm start и работает.
База данных: SQLite
Второй камень преткновения — выбор БД. Разработчики частенько тянут в простые проекты PostgreSQL или MySQL. Для пет-проекта или MVP это overkill. Мы берем SQLite.
-
Это просто файл в папке. Нет отдельного процесса сервера.
-
Нет сетевых настроек и задержек (база лежит рядом с кодом).
-
Идеально подходит для чат-ботов, где запросы идут последовательно.
Если ваш бот вдруг станет популярным и упрется в конкуренцию записи — тогда вы переедете на Postgres. А пока не тратим время на DevOps.
Архитектура: Проблема идентификации
Логика бота кажется тривиальной:
-
Клиент пишет -> Бот пересылает Админу.
-
Админ отвечает -> Бот пересылает Клиенту.
Сложность кроется во втором пункте. Когда Админ нажимает кнопку "Ответить" (Reply) в своем клиенте, бот видит входящее сообщение от Админа. Но как узнать, какому именно клиенту адресован ответ? В самом тексте сообщения ID клиента нет.
Нам нужно построить Карту сообщений (Message Map). Мы будем запоминать ID каждого сообщения, которое бот шлет Админу, и связывать его с ID Клиента.
Алгоритм:
-
Бот получает сообщение от Клиента.
-
Пересылает его Админу.
-
Запоминает в БД:
ID_этого_сообщения -> ID_клиента. -
Админ делает Reply на это сообщение.
-
Бот видит ID, на который ответили, находит в БД клиента и шлет ответ.
Пишем код
Подготовка
Создаем проект и устанавливаем зависимости. Нам понадобятся сама библиотека бота, драйвер SQLite и пакет для переменных окружения.
npm init -y
npm install @maxhub/max-bot-api sqlite sqlite3 dotenv
Создаем файл .env. Токен бота берем в интеграциях на business.max.ru, а OWNER_ID — это ваш личный ID в Max (его можно узнать в логах при первом запуске).
BOT_TOKEN=ваш_токен_здесь
OWNER_ID=12345678
Настройка БД
Подключаем SQLite и создаем таблицу для маппинга. Обратите внимание на использование async/await — библиотека sqlite отлично с ним дружит.
import { open } from 'sqlite';
import sqlite3 from 'sqlite3';
let db;
(async () => {
db = await open({ filename: './database.sqlite', driver: sqlite3.Database });
// Таблица для связи ID сообщения -> ID клиента
await db.exec(`
CREATE TABLE IF NOT EXISTS reply_map (
owner_msg_mid TEXT PRIMARY KEY,
client_user_id INTEGER
)
`);
console.log('База данных готова');
})();
Входящий поток (Клиент -> Админ)
Главный нюанс: метод sendMessageToUser возвращает объект отправленного сообщения. Нам нужно достать из него mid (Message ID), чтобы сохранить в базу. Без этого мы не сможем "привязать" ответ админа к конкретному пользователю.
const ownerId = Number(process.env.OWNER_ID); // Ваш ID
bot.on('message_created', async (ctx) => {
const msg = ctx.message;
const senderId = msg.sender.user_id;
const text = msg.body.text;
// Если пишет КЛИЕНТ (не админ)
if (senderId !== ownerId) {
const forwardText = `📩 **Сообщение от ${msg.sender.first_name}** (ID: ${senderId}):nn${text}`;
// Отправляем админу с Markdown-разметкой
const sentMsg = await bot.api.sendMessageToUser(ownerId, forwardText, { format: 'markdown' });
// ГЛАВНЫЙ МОМЕНТ: Сохраняем связь
if (sentMsg && sentMsg.body && sentMsg.body.mid) {
await db.run('INSERT OR REPLACE INTO reply_map (owner_msg_mid, client_user_id) VALUES (?, ?)',
[sentMsg.body.mid, senderId]);
}
return;
}
// ... здесь будет логика ответов
});
Исходящий поток (Админ -> Клиент)
Теперь самое интересное. Как понять, что Админ нажал кнопку "Ответить"? Изучая объект сообщения (msg), который присылает API, можно заметить поле link. Оно находится в корне объекта, на уровне с body и sender.
Структура JSON при Reply выглядит так:
{
"body": { "text": "ответ", ... },
"sender": { ... },
"link": { // <-- Идентификатор ответа
"type": "reply",
"message": {
"mid": "mid.исходного_сообщения..." // ID сообщения, на которое ответили
}
}
}
Реализуем поиск получателя. Если админ просто пишет текст (не Reply), отправляем последнему активному клиенту (fallback).
// Продолжение внутри bot.on...
// Если пишет ВЛАДЕЛЕЦ
if (senderId === ownerId) {
// Проверяем, что это именно Reply
if (msg.link && msg.link.type === 'reply') {
// Достаем ID сообщения, на которое ответили
const repliedMsgMid = msg.link.message.mid;
// Ищем в нашей базе клиента
const target = await db.get('SELECT client_user_id FROM reply_map WHERE owner_msg_mid = ?', repliedMsgMid);
if (target) {
await bot.api.sendMessageToUser(target.client_user_id, text);
await ctx.reply(`✅ Ответ отправлен пользователю ID: ${target.client_user_id}`);
} else {
await ctx.reply('⚠️ Пользователь не найден в базе (возможно, старое сообщение).');
}
return;
}
// Fallback: Если Админ просто пишет текст, отправляем последнему активному
// (Для простоты MVP сохраняем lastClient в глобальной переменной)
}
Итог
У нас на руках рабочий MVP бота обратной связи.
-
Запуск за 5 минут. Не нужны серверы, настройки портов и SSL-сертификаты. Скачал, вставил токен, работает.
-
Удобство для админа. Вы общаетесь с клиентами привычным способом — через кнопку "Ответить" (Reply), как в обычном чате. Бот сам разберется, кому отправить текст.
-
Надежность. Вся база контактов хранится в одном файле. Проще всего в мире бэкапить и переносить.
Код проекта доступен на GitHub: mikhail-klimenko/max-feedback-bot
Это только начало. Я буду рад, если вы присоединитесь к разработке — предлагайте пул-реквесты, заводите Issues с идеями или багами. Давайте сделаем инструмент для обратной связи в Max вместе!
В следующих частях добавим поддержку медиа и админку.
Автор: klimenkome
