Google Apps Script библиотека для MAX Bot API: сообщения, команды, webhook-обработчики, inline-клавиатуры и загрузка медиа — на обычном JavaScript.
1dUzlK1qVuXOB8OOKgWazEBtCSbFRqIUv9F0-yQXM4K9xk6UlH-Ks2aOU
- Подключение
- Готовый webhook-бот
- Создание клиента
- Сообщения
- Обработчики и ctx
- Клавиатуры
- Фото, видео, аудио и файлы
- Команды и подписки
- Ошибки
- Свой класс бота
- Документация
- Откройте Google Apps Script и создайте проект.
- Нажмите
+рядом с пунктом Библиотеки. - Вставьте ID MaxBot из раздела выше.
- Выберите опубликованную версию
4и идентификаторMaxBot. - Нажмите Добавить.
Все примеры ниже используют именно этот идентификатор:
vartoken=PropertiesService.getScriptProperties().getProperty("MAX_BOT_TOKEN");varbot=MaxBot.create(token);Откройте Настройки проекта → Свойства скрипта и добавьте:
| Свойство | Что записать |
|---|---|
MAX_BOT_TOKEN | токен бота из кабинета MAX |
MAX_WEBHOOK_SECRET | длинную случайную строку для защиты Web App URL |
В коде значения читаются так:
varproperties=PropertiesService.getScriptProperties();vartoken=properties.getProperty("MAX_BOT_TOKEN");varwebhookSecret=properties.getProperty("MAX_WEBHOOK_SECRET");Токен и секрет не нужно вставлять прямо в исходный код.
Ниже — цельный пример: создание клиента, команды, обработчики сообщений и кнопок, doPost(e), регистрация команд и подписка на webhook.
varproperties=PropertiesService.getScriptProperties();varbot=MaxBot.create(properties.getProperty("MAX_BOT_TOKEN"),{apiVersion: "v1",webhookSecret: properties.getProperty("MAX_WEBHOOK_SECRET"),parseMode: "markdown",});functionwelcome(ctx){returnctx.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){vartext=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);});functiondoPost(e){returnbot.handleWebhook(e);}functiongetWebhookUrl_(){vardeploymentId="<DEPLOYMENT_ID>";varsecret=properties.getProperty("MAX_WEBHOOK_SECRET");return"https://script.google.com/macros/s/"+deploymentId+"/exec?secret="+encodeURIComponent(secret);}functionsetupBot(){bot.setMyCommands([{name: "start",description: "Открыть меню"},{name: "echo",description: "Повторить текст"},]);returnbot.subscribe(getWebhookUrl_(),{updateTypes: ["bot_started","message_created","message_edited","message_callback",],});}functionremoveWebhook(){returnbot.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клиента.
Основная сигнатура:
varbot=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".
Настройки необязательны:
varbot=MaxBot.create(properties.getProperty("MAX_BOT_TOKEN"));Формат конкретного сообщения переопределяет parseMode:
bot.sendMessage({chatId: 123,text: "<b>HTML для одного сообщения</b>",options: {format: "html"},});sendMessage() принимает один объект. Укажите ровно одного адресата: chatId или userId.
varresult=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.
Сообщения также можно получать, редактировать и удалять:
varmessages=bot.getMessages({chatId: 123,count: 20});varmessage=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 можно вызвать диспетчер напрямую:
varctx=bot.handleUpdate(update);console.log(ctx.updateType);Fluent-builder начинает с первой строки, а .row() переносит следующие кнопки на новую:
varkeyboard=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){returnctx.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){varnumber=ctx.match[1];ctx.answerCallback({notification: "Вы нажали "+number});returnctx.reply("Вы выбрали цифру: "+number);});Чтобы получать нажатия, добавьте message_callback в updateTypes webhook-подписки.
В GAS каждый webhook запускается отдельно, поэтому текущий шаг и ответы сохраняются в Script Properties. Ключ содержит chatId и userId: пользователи могут проходить опрос параллельно в разных чатах.
varsurveyProperties=PropertiesService.getScriptProperties();functionsurveyKey_(ctx){return"survey:"+ctx.chatId+":"+ctx.user.user_id;}functionreadSurvey_(ctx){varvalue=surveyProperties.getProperty(surveyKey_(ctx));returnvalue ? JSON.parse(value) : null;}functionwriteSurvey_(ctx,survey){surveyProperties.setProperty(surveyKey_(ctx),JSON.stringify(survey));}bot.command("survey",function(ctx){writeSurvey_(ctx,{step: "name"});returnctx.reply("Как вас зовут?");});bot.command("cancel",function(ctx){surveyProperties.deleteProperty(surveyKey_(ctx));returnctx.reply("Опрос отменён.");});bot.on("message_created",function(ctx){varsurvey=readSurvey_(ctx);vartext=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);returnctx.reply("Укажите пол:",{keyboard: bot.keyboard.callback("Мужской","survey:gender:male").callback("Женский","survey:gender:female").callback("Не указывать","survey:gender:none").build(),});}if(survey.step==="age"){varage=Number(text);if(!Number.isInteger(age)||age<1||age>120){returnctx.reply("Введите возраст целым числом от 1 до 120.");}survey.age=age;survey.step="country";writeSurvey_(ctx,survey);returnctx.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){varsurvey=readSurvey_(ctx);vargenders={male: "Мужской",female: "Женский",none: "Не указывать",};if(!survey||survey.step!=="gender"){returnctx.answerCallback({notification: "Этот шаг уже завершён"});}survey.gender=genders[ctx.match[1]];survey.step="age";writeSurvey_(ctx,survey);ctx.answerCallback({notification: "Выбрано: "+survey.gender});returnctx.reply("Сколько вам лет?");});bot.action(/^survey:country:(russia|usa|italy|other)$/,function(ctx){varsurvey=readSurvey_(ctx);varcountries={russia: "Россия",usa: "США",italy: "Италия",other: "Другое",};if(!survey||survey.step!=="country"){returnctx.answerCallback({notification: "Опрос уже завершён"});}survey.country=countries[ctx.match[1]];surveyProperties.deleteProperty(surveyKey_(ctx));ctx.answerCallback({notification: "Страна: "+survey.country});returnctx.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.*:
varkeyboard=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:
varfileBlob=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".
functioninstallCommands(){returnbot.setMyCommands([{name: "start",description: "Открыть меню"},{name: "help",description: "Показать помощь"},]);}functionshowCommands(){console.log(JSON.stringify(bot.getMyCommands(),null,2));}functionremoveCommands(){returnbot.deleteMyCommands();}varurl=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 доступен:
varupdates=bot.getUpdates({limit: 20,timeout: 0,marker: null,types: ["message_created"],});HTTP-ответ вне диапазона 2xx превращается в MaxBot.MaxError:
try{bot.sendMessage({chatId: 123,text: "Проверка"});}catch(error){if(errorinstanceofMaxBot.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{throwerror;}}У MaxError есть поля status, code, description, response, responseText, method и path. Сетевые ошибки, ошибки JSON и исключения из обработчиков передаются вызывающему коду без автоматических повторов.
MaxBot.Bot можно наследовать и добавлять методы своего приложения:
classShopBotextendsMaxBot.Bot{sendOrder(chatId,orderId){returnthis.sendMessage({chatId: chatId,text: "Заказ №"+orderId+" принят",});}}varproperties=PropertiesService.getScriptProperties();varshopBot=newShopBot(properties.getProperty("MAX_BOT_TOKEN"),{apiVersion: "v1",webhookSecret: properties.getProperty("MAX_WEBHOOK_SECRET"),parseMode: "markdown",});shopBot.command("order",function(ctx){varorderId=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.