Google Apps Script библиотека для MAX Bot API: сообщения, команды, webhook-обработчики, inline-клавиатуры и загрузка медиа — на обычном JavaScript.
1dUzlK1qVuXOB8OOKgWazEBtCSbFRqIUv9F0-yQXM4K9xk6UlH-Ks2aOU
- Подключение
- Готовый webhook-бот
- Создание клиента
- Сообщения
- Обработчики и ctx
- Клавиатуры
- Фото, видео, аудио и файлы
- Команды и подписки
- Ошибки
- Свой класс бота
- Документация
- Откройте Google Apps Script и создайте проект.
- Нажмите
+рядом с пунктом Библиотеки. - Вставьте ID MaxBot из раздела выше.
- Выберите опубликованную версию
4и идентификаторMaxBot. - Нажмите Добавить.
Все примеры ниже используют именно этот идентификатор:
var token = PropertiesService.getScriptProperties().getProperty("MAX_BOT_TOKEN");
var bot = MaxBot.create(token);Откройте Настройки проекта → Свойства скрипта и добавьте:
| Свойство | Что записать |
|---|---|
MAX_BOT_TOKEN |
токен бота из кабинета MAX |
MAX_WEBHOOK_SECRET |
длинную случайную строку для защиты Web App URL |
В коде значения читаются так:
var properties = PropertiesService.getScriptProperties();
var token = properties.getProperty("MAX_BOT_TOKEN");
var webhookSecret = properties.getProperty("MAX_WEBHOOK_SECRET");Токен и секрет не нужно вставлять прямо в исходный код.
Ниже — цельный пример: создание клиента, команды, обработчики сообщений и кнопок, doPost(e), регистрация команд и подписка на webhook.
var properties = PropertiesService.getScriptProperties();
var bot = MaxBot.create(properties.getProperty("MAX_BOT_TOKEN"), {
apiVersion: "v1",
webhookSecret: properties.getProperty("MAX_WEBHOOK_SECRET"),
parseMode: "markdown",
});
function welcome(ctx) {
return ctx.reply("**Привет!** Я уже работаю 👋", {
keyboard: bot.keyboard
.callback("Помощь", "help")
.link("Открыть MAX", "https://max.ru")
.build(),
});
}
bot
.on("bot_started", welcome)
.command("start", welcome);
bot.command("echo", function (ctx) {
var text = ctx.match && ctx.match[1];
ctx.reply(text || "Напишите текст после команды: /echo привет");
});
bot.hears(/привет/i, function (ctx) {
ctx.reply("Привет! Нажмите /start, чтобы открыть меню.");
});
bot.action("help", function (ctx) {
ctx.answerCallback({ notification: "Команды: /start и /echo" });
});
bot.on("message_edited", function (ctx) {
console.log("Изменено сообщение: " + ctx.messageId);
});
function doPost(e) {
return bot.handleWebhook(e);
}
function getWebhookUrl_() {
var deploymentId = "<DEPLOYMENT_ID>";
var secret = properties.getProperty("MAX_WEBHOOK_SECRET");
return "https://script.google.com/macros/s/" + deploymentId +
"/exec?secret=" + encodeURIComponent(secret);
}
function setupBot() {
bot.setMyCommands([
{ name: "start", description: "Открыть меню" },
{ name: "echo", description: "Повторить текст" },
]);
return bot.subscribe(getWebhookUrl_(), {
updateTypes: [
"bot_started",
"message_created",
"message_edited",
"message_callback",
],
});
}
function removeWebhook() {
return bot.unsubscribe(getWebhookUrl_());
}- Выберите Развернуть → Новое развертывание → Веб-приложение.
- Укажите запуск от своего имени и доступ для всех, затем создайте развертывание.
- Замените
<DEPLOYMENT_ID>вgetWebhookUrl_(). - Один раз запустите
setupBot()из редактора Apps Script и подтвердите разрешения. - Напишите боту
/start.
Секрет находится в query-параметре URL:
https://script.google.com/macros/s/<DEPLOYMENT_ID>/exec?secret=<MAX_WEBHOOK_SECRET>
handleWebhook(e) сравнивает e.parameter.secret со значением webhookSecret, затем разбирает e.postData.contents, запускает обработчики и возвращает ответ OK. Если секрет отсутствует или не совпадает, update не обрабатывается.
Параметр
secretметодаsubscribe(url, options)— отдельное поле подписки MAX. В примере для Apps Script защитаdoPost(e)выполняется именно через query-параметр URL и настройкуwebhookSecretклиента.
Основная сигнатура:
var bot = MaxBot.create(token, {
apiVersion: "v1",
webhookSecret: "QUERY_SECRET",
parseMode: "markdown",
});| Настройка | Значения | Назначение |
|---|---|---|
apiVersion |
"v1" или "v2" |
выбирает версию API; по умолчанию используется v1 |
webhookSecret |
непустая строка | ожидаемое значение e.parameter.secret |
parseMode |
"markdown" или "html" |
формат сообщений по умолчанию |
apiVersion: "v1"используетhttps://platform-api.max.ruс обычной проверкой HTTPS-сертификата и выбран по умолчанию для совместимости с GAS.apiVersion: "v2"используетhttps://platform-api2.max.ru. Библиотека передаётvalidateHttpsCertificates: false, но Google Apps Script всё равно может завершить запрос сSSL Errorиз-за сертификата Минцифры.
Если v2 недоступен из GAS, явно используйте apiVersion: "v1".
Настройки необязательны:
var bot = MaxBot.create(properties.getProperty("MAX_BOT_TOKEN"));Формат конкретного сообщения переопределяет parseMode:
bot.sendMessage({
chatId: 123,
text: "<b>HTML для одного сообщения</b>",
options: { format: "html" },
});sendMessage() принимает один объект. Укажите ровно одного адресата: chatId или userId.
var result = bot.sendMessage({
chatId: 123,
text: "Выберите действие",
options: {
notify: false,
disableLinkPreview: true,
keyboard: bot.keyboard
.callback("Готово", "done")
.link("Сайт", "https://max.ru")
.build(),
},
});
console.log(JSON.stringify(result, null, 2));Личное сообщение пользователю отправляется тем же методом:
bot.sendMessage({
userId: 456,
text: "Это личное сообщение",
});В одном вызове нельзя одновременно передать chatId и userId. Результат каждого API-метода — разобранный JSON-ответ MAX; пустой успешный HTTP-ответ возвращается как null.
Сообщения также можно получать, редактировать и удалять:
var messages = bot.getMessages({ chatId: 123, count: 20 });
var message = bot.getMessage("MESSAGE_ID");
bot.editMessage("MESSAGE_ID", { text: "Исправленный текст" });
bot.deleteMessage("MESSAGE_ID");MaxBot предоставляет четыре вида обработчиков:
bot.on("message_created", function (ctx) {
console.log(ctx.updateType);
});
bot.command("start", function (ctx) {
ctx.reply("Команда /start");
});
bot.hears(/^заказ (\d+)$/i, function (ctx) {
ctx.reply("Ищу заказ №" + ctx.match[1]);
});
bot.action(/^item:(\d+)$/, function (ctx) {
ctx.answerCallback({ notification: "Выбран товар " + ctx.match[1] });
});on(updateType, handler)реагирует на указанныйupdate_type;"*"подходит для любого типа.command(name, handler)ищет команду в начале сообщения без учёта регистра. Текст после команды доступен вctx.match[1].hears(stringOrRegExp, handler)проверяет текст событияmessage_created. Строка должна совпасть целиком, регулярное выражение работает как обычно.action(stringOrRegExp, handler)проверяетcallback.payloadсобытияmessage_callback.
Регистрации можно объединять в цепочку. Все совпавшие обработчики выполняются синхронно в порядке регистрации:
bot
.on("message_created", logMessage)
.hears("ping", function (ctx) { ctx.reply("pong"); })
.command("start", showMenu);В контексте обработчика доступны:
| Поле или метод | Что содержит |
|---|---|
ctx.update |
исходный update MAX |
ctx.updateType |
значение update.update_type |
ctx.message |
сообщение или null |
ctx.callback |
callback или null |
ctx.user |
пользователь callback, отправитель сообщения или update.user |
ctx.chatId |
ID текущего чата или null |
ctx.messageId |
message.body.mid или null |
ctx.match |
результат RegExp либо null |
ctx.reply(text, options) |
отправляет сообщение в текущий чат |
ctx.answerCallback(answer) |
отвечает на текущий callback |
Для уже разобранного update можно вызвать диспетчер напрямую:
var ctx = bot.handleUpdate(update);
console.log(ctx.updateType);Fluent-builder начинает с первой строки, а .row() переносит следующие кнопки на новую:
var keyboard = bot.keyboard
.callback("Да", "confirm")
.callback("Нет", "cancel")
.row()
.requestContact("Отправить контакт")
.requestGeoLocation("Отправить геопозицию", true)
.row()
.openApp("Открыть приложение", "https://example.com/app")
.message("Написать оператору")
.row()
.clipboard("Скопировать код", "MAX-2026")
.link("Открыть MAX", "https://max.ru")
.build();
bot.sendMessage({
chatId: 123,
text: "Что сделать?",
options: { keyboard: keyboard },
});Команда /numbers показывает пять кнопок. Значение после number: передаётся в callback.payload, а ctx.match[1] содержит выбранную цифру:
bot.command("numbers", function (ctx) {
return ctx.reply("Выберите цифру:", {
keyboard: bot.keyboard
.callback("1", "number:1")
.callback("2", "number:2")
.callback("3", "number:3")
.callback("4", "number:4")
.callback("5", "number:5")
.build(),
});
});
bot.action(/^number:([1-5])$/, function (ctx) {
var number = ctx.match[1];
ctx.answerCallback({ notification: "Вы нажали " + number });
return ctx.reply("Вы выбрали цифру: " + number);
});Чтобы получать нажатия, добавьте message_callback в updateTypes webhook-подписки.
В GAS каждый webhook запускается отдельно, поэтому текущий шаг и ответы сохраняются в Script Properties. Ключ содержит chatId и userId: пользователи могут проходить опрос параллельно в разных чатах.
var surveyProperties = PropertiesService.getScriptProperties();
function surveyKey_(ctx) {
return "survey:" + ctx.chatId + ":" + ctx.user.user_id;
}
function readSurvey_(ctx) {
var value = surveyProperties.getProperty(surveyKey_(ctx));
return value ? JSON.parse(value) : null;
}
function writeSurvey_(ctx, survey) {
surveyProperties.setProperty(surveyKey_(ctx), JSON.stringify(survey));
}
bot.command("survey", function (ctx) {
writeSurvey_(ctx, { step: "name" });
return ctx.reply("Как вас зовут?");
});
bot.command("cancel", function (ctx) {
surveyProperties.deleteProperty(surveyKey_(ctx));
return ctx.reply("Опрос отменён.");
});
bot.on("message_created", function (ctx) {
var survey = readSurvey_(ctx);
var text = ctx.message && ctx.message.body && ctx.message.body.text;
if (!survey || !text || /^\//.test(text)) return;
text = text.trim();
if (!text) return;
if (survey.step === "name") {
survey.name = text;
survey.step = "gender";
writeSurvey_(ctx, survey);
return ctx.reply("Укажите пол:", {
keyboard: bot.keyboard
.callback("Мужской", "survey:gender:male")
.callback("Женский", "survey:gender:female")
.callback("Не указывать", "survey:gender:none")
.build(),
});
}
if (survey.step === "age") {
var age = Number(text);
if (!Number.isInteger(age) || age < 1 || age > 120) {
return ctx.reply("Введите возраст целым числом от 1 до 120.");
}
survey.age = age;
survey.step = "country";
writeSurvey_(ctx, survey);
return ctx.reply("Выберите страну:", {
keyboard: bot.keyboard
.callback("Россия", "survey:country:russia")
.callback("США", "survey:country:usa")
.callback("Италия", "survey:country:italy")
.callback("Другое", "survey:country:other")
.build(),
});
}
});
bot.action(/^survey:gender:(male|female|none)$/, function (ctx) {
var survey = readSurvey_(ctx);
var genders = {
male: "Мужской",
female: "Женский",
none: "Не указывать",
};
if (!survey || survey.step !== "gender") {
return ctx.answerCallback({ notification: "Этот шаг уже завершён" });
}
survey.gender = genders[ctx.match[1]];
survey.step = "age";
writeSurvey_(ctx, survey);
ctx.answerCallback({ notification: "Выбрано: " + survey.gender });
return ctx.reply("Сколько вам лет?");
});
bot.action(/^survey:country:(russia|usa|italy|other)$/, function (ctx) {
var survey = readSurvey_(ctx);
var countries = {
russia: "Россия",
usa: "США",
italy: "Италия",
other: "Другое",
};
if (!survey || survey.step !== "country") {
return ctx.answerCallback({ notification: "Опрос уже завершён" });
}
survey.country = countries[ctx.match[1]];
surveyProperties.deleteProperty(surveyKey_(ctx));
ctx.answerCallback({ notification: "Страна: " + survey.country });
return ctx.reply([
"Анкета заполнена:",
"Имя: " + survey.name,
"Пол: " + survey.gender,
"Возраст: " + survey.age,
"Страна: " + survey.country,
].join("\n"), { format: null });
});Для этого примера подпишите webhook на message_created и message_callback. Команды /survey и /cancel также можно добавить через bot.setMyCommands().
Доступные методы builder: callback, link, requestContact, requestGeoLocation, openApp, message, clipboard, row и build.
Для ручной сборки используйте MaxBot.inlineKeyboard() и MaxBot.button.*:
var keyboard = MaxBot.inlineKeyboard([
[
MaxBot.button.callback("ОК", "ok"),
MaxBot.button.link("Документация", "https://dev.max.ru/docs-api"),
],
]);Готовые вложения создаются через MaxBot.attachment.image(), .video(), .audio(), .file(), .sticker(), .contact(), .location() и .share().
Media builder возвращает фрагмент { text, options }, который удобно добавить в единственный объект sendMessage().
bot.sendMessage({
chatId: 123,
...bot.photo
.url("https://example.com/photo.jpg")
.caption("**Красивое фото!**")
.format("markdown")
.keyboard(bot.keyboard.callback("Нравится", "like").build())
.build(),
});Изображение с внешним URL передаётся в MAX напрямую. Для видео, аудио и файла библиотека сначала скачивает URL как GAS Blob, затем загружает его в MAX:
bot.sendMessage({
chatId: 123,
...bot.video
.url("https://example.com/video.mp4")
.caption("Видео")
.build(),
});Можно передать готовый Blob из Google Drive:
var fileBlob = DriveApp.getFileById("GOOGLE_DRIVE_FILE_ID").getBlob();
bot.sendMessage({
chatId: 123,
...bot.file.blob(fileBlob).caption("Документ").build(),
});Или повторно использовать полученный ранее upload token:
bot.sendMessage({
chatId: 123,
...bot.audio.token("UPLOAD_TOKEN").caption("Аудио").build(),
});Для ручной загрузки Blob доступен bot.uploadMedia(type, blob), где type — "image", "video", "audio" или "file".
function installCommands() {
return bot.setMyCommands([
{ name: "start", description: "Открыть меню" },
{ name: "help", description: "Показать помощь" },
]);
}
function showCommands() {
console.log(JSON.stringify(bot.getMyCommands(), null, 2));
}
function removeCommands() {
return bot.deleteMyCommands();
}var url = getWebhookUrl_();
bot.subscribe(url, {
updateTypes: [
"bot_added",
"bot_started",
"bot_stopped",
"bot_removed",
"chat_title_changed",
"dialog_cleared",
"dialog_muted",
"dialog_unmuted",
"dialog_removed",
"message_callback",
"message_created",
"message_edited",
"message_removed",
"user_added",
"user_removed",
],
});
console.log(JSON.stringify(bot.getSubscriptions(), null, 2));
bot.unsubscribe(url);Передавайте только нужные вашему боту события. На 12 августа 2026 года официальный объект Update содержит такие типы:
| Тип | Когда приходит |
|---|---|
bot_added |
бот добавлен в чат или канал |
bot_started |
пользователь начал или возобновил общение с ботом |
bot_stopped |
пользователь остановил или удалил бота в настройках |
bot_removed |
бот удалён из чата или канала |
chat_title_changed |
изменено название чата или канала |
dialog_cleared |
пользователь очистил историю диалога с ботом |
dialog_muted |
пользователь отключил уведомления в диалоге |
dialog_unmuted |
пользователь включил уведомления в диалоге |
dialog_removed |
пользователь удалил диалог с ботом |
message_callback |
нажата callback-кнопка |
message_created |
отправлено сообщение или опубликован пост |
message_edited |
сообщение или пост отредактирован |
message_removed |
сообщение или пост удалён |
user_added |
пользователь добавлен в чат или канал либо перешёл по ссылке |
user_removed |
пользователь удалён или вышел из чата или канала |
subscribe() преобразует updateTypes в поле API update_types и принимает отдельный параметр secret, если он нужен вашему сценарию. Для диагностического получения обновлений без webhook доступен:
var updates = bot.getUpdates({
limit: 20,
timeout: 0,
marker: null,
types: ["message_created"],
});HTTP-ответ вне диапазона 2xx превращается в MaxBot.MaxError:
try {
bot.sendMessage({ chatId: 123, text: "Проверка" });
} catch (error) {
if (error instanceof MaxBot.MaxError) {
console.error("HTTP status: " + error.status);
console.error("MAX code: " + error.code);
console.error("Описание: " + error.description);
console.error("Метод и путь: " + error.method + " " + error.path);
console.error("Ответ: " + error.responseText);
} else {
throw error;
}
}У MaxError есть поля status, code, description, response, responseText, method и path. Сетевые ошибки, ошибки JSON и исключения из обработчиков передаются вызывающему коду без автоматических повторов.
MaxBot.Bot можно наследовать и добавлять методы своего приложения:
class ShopBot extends MaxBot.Bot {
sendOrder(chatId, orderId) {
return this.sendMessage({
chatId: chatId,
text: "Заказ №" + orderId + " принят",
});
}
}
var properties = PropertiesService.getScriptProperties();
var shopBot = new ShopBot(properties.getProperty("MAX_BOT_TOKEN"), {
apiVersion: "v1",
webhookSecret: properties.getProperty("MAX_WEBHOOK_SECRET"),
parseMode: "markdown",
});
shopBot.command("order", function (ctx) {
var orderId = ctx.match && ctx.match[1];
if (orderId) shopBot.sendOrder(ctx.chatId, orderId);
});Наследуемый класс получает тот же конструктор, API-методы, handlers и builders, что и клиент из MaxBot.create().
- Структура библиотеки и основные сущности
- Полный справочник методов
- Официальная документация MAX Bot API
Готовые примеры в репозитории:
Тесты запускаются локально на Node.js без обращения к настоящему API:
npm testПроект распространяется по лицензии MIT и не является официальной библиотекой MAX.