Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

34 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

MaxBot

Donate Apps Script javascript GitHub code size in bytes GitHub last commit

Google Apps Script библиотека для MAX Bot API: сообщения, команды, webhook-обработчики, inline-клавиатуры и загрузка медиа — на обычном JavaScript.

📦 ID библиотеки

1dUzlK1qVuXOB8OOKgWazEBtCSbFRqIUv9F0-yQXM4K9xk6UlH-Ks2aOU

📋 Содержание

🚀 Подключение

1. Добавьте библиотеку

  1. Откройте Google Apps Script и создайте проект.
  2. Нажмите + рядом с пунктом Библиотеки.
  3. Вставьте ID MaxBot из раздела выше.
  4. Выберите опубликованную версию 4 и идентификатор MaxBot.
  5. Нажмите Добавить.

Все примеры ниже используют именно этот идентификатор:

var token = PropertiesService.getScriptProperties().getProperty("MAX_BOT_TOKEN");
var bot = MaxBot.create(token);

2. Сохраните секреты в Script Properties

Откройте Настройки проекта → Свойства скрипта и добавьте:

Свойство Что записать
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");

Токен и секрет не нужно вставлять прямо в исходный код.

⚡ Готовый webhook-бот

Ниже — цельный пример: создание клиента, команды, обработчики сообщений и кнопок, 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_());
}

Опубликуйте Web App

  1. Выберите Развернуть → Новое развертывание → Веб-приложение.
  2. Укажите запуск от своего имени и доступ для всех, затем создайте развертывание.
  3. Замените <DEPLOYMENT_ID> в getWebhookUrl_().
  4. Один раз запустите setupBot() из редактора Apps Script и подтвердите разрешения.
  5. Напишите боту /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" формат сообщений по умолчанию

Версии MAX API

  • 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");

🎯 Обработчики и ctx

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().

Изображение по URL

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();
}

Webhook-подписки

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().

📚 Документация

Готовые примеры в репозитории:

🧪 Проверка проекта

Тесты запускаются локально на Node.js без обращения к настоящему API:

npm test

📄 Лицензия

Проект распространяется по лицензии MIT и не является официальной библиотекой MAX.

About

Google Apps Script библиотека для работы с методами API MAX с поддержкой вебхуков, созданием медиафайлов и клавиатур.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages