CloudGram

CloudGram API

Один модуль для всего: боты, свой аккаунт и доступ к аккаунтам пользователей вашего сайта. Адреса, заголовки и опрос обновлений настроены внутри — вы вызываете функции.

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

Установка модуля и справочник функций: что делает каждая.

Открыть API

Ключи доступа

Получить client_id и client_secret для работы со своим аккаунтом.

Создать доступ

CloudGram API

Один модуль: боты, свой аккаунт и доступ к аккаунтам пользователей вашего сайта.

Адреса, заголовки, коды ошибок и опрос обновлений настроены внутри модуля. Снаружи — только функции из этого справочника.

Первый бот за минуту

Бот создаётся не в коде, а в переписке: у CloudGram есть служебный бот Bot Father, он всё и оформит.

Найдите Bot Father

В поиске чатов наберите @fatherbot и откройте его.

Нажмите «Меню»

Слева от поля ввода появится кнопка «Меню» — она открывает мини-приложение, где живёт всё управление ботами. Печатать команды не нужно.

«Создать бота»

Придумайте имя (его увидят люди в чатах) и юзернейм — латиница, цифры и подчёркивания, обязательно заканчивается на bot. Например: my_helper_bot.

Заберите токен

Он появится в карточке бота, скрытый точками. Нажмите «Показать», затем «Скопировать».

Вставьте токен в код

Скачайте модуль, подставьте токен — и бот отвечает. Настраивать больше нечего.

const cloudgram = require("./cloudgram.js");
const bot = cloudgram.bot("ВАШ_ТОКЕН");

bot.onMessage((msg) => msg.reply("Привет, " + msg.from.first_name + "!"));
bot.start();

В том же мини-приложении задаются аватарка, описание, команды-подсказки и мини-приложение самого бота.

Что такое токен и почему его берегут

Токен — это и логин, и пароль бота в одной строке вида 5:c229bf39…. Сервер не спрашивает больше ничего: пришёл верный токен — значит, пришёл владелец бота.

Что можно с токеномПоследствие
Читать входящие сообщениячужой человек увидит переписку ваших пользователей с ботом
Писать от имени ботаможно рассылать что угодно от вашего имени
Менять кнопки и мини-приложениекнопку можно увести на чужой сайт
Токен нельзя выкладывать в открытый код, в скриншоты и в переписку. Всё, что сделано с вашим токеном, считается сделанным вами — независимо от того, как он попал к другому человеку.

Как хранить правильно:

  • держите токен в переменной окружения, а не в тексте программы;
  • не коммитьте файл с токеном в репозиторий — добавьте его в .gitignore;
  • если токен где-то засветился, сразу нажмите «Сбросить» в карточке бота: старый перестанет работать в ту же секунду.
const bot = cloudgram.bot(process.env.CLOUDGRAM_TOKEN);

Установка

Модуль без зависимостей: один файл, ставить ничего не нужно.

Node.js 12+

Скачайте cloudgram.js и положите рядом со своим скриптом:

const cloudgram = require("./cloudgram.js");

Python 3.8+

Скачайте cloudgram.py и положите рядом со своим скриптом:

import cloudgram

Быстрый старт

Бот отвечает на сообщения

const cloudgram = require("./cloudgram.js");
const bot = cloudgram.bot("ТОКЕН_БОТА");

bot.onMessage((msg) => {
  msg.reply("Привет, " + msg.from.first_name + "!");
});

bot.start();
import cloudgram
bot = cloudgram.bot("ТОКЕН_БОТА")

@bot.on_message
def handle(msg):
    msg.reply("Привет, " + msg.sender_name + "!")

bot.start()

Свой аккаунт

const account = cloudgram.account("CLIENT_ID", "CLIENT_SECRET");

const chats = await account.chats();
await account.send(chats[0].id, "Сообщение из скрипта");
account = cloudgram.account("CLIENT_ID", "CLIENT_SECRET")

chats = account.chats()
account.send(chats[0]["id"], "Сообщение из скрипта")

Сайт подключает аккаунт пользователя

Шаг 1 — отправьте пользователя на страницу согласия. Шаг 2 — обменяйте код из адреса возврата на подключение.

const site = cloudgram.site({ name: "Моя CRM", redirectUri: "https://ваш-сайт/callback" });

// 1. куда отправить пользователя
const url = site.link(myRandomState);

// 2. на адресе возврата пришёл code
const session = await site.connect(code);
const chats = await session.chats();
site = cloudgram.site("https://ваш-сайт/callback", name="Моя CRM")

url = site.link(my_random_state)

session = site.connect(code)
chats = session.chats()
Обменивайте код и храните токен только на своём сервере, не в браузере.

Ключи

Что нужноГде взять
Токен ботакоманда /newbot в чате с ботом @newbot, там же /token для сброса
client_id и client_secretстраница создания доступа, нужна корона
Разрешение адреса возвратаадрес добавляет владелец CloudGram; проверить — site.checkRedirect()

Функции: бот

Создание: cloudgram.bot(token) — в Python так же.

Приём сообщений

Node.jsPythonЧто делает
bot.onMessage(fn)@bot.on_messageвызывает fn на каждое входящее сообщение
bot.onCommand(cmd, fn)@bot.on_command(cmd)вызывает fn только на указанную команду, например "/start"
bot.onError(fn)@bot.on_errorвызывает fn при ошибке опроса; без него ошибки печатаются в консоль
bot.start()bot.start()начинает получать сообщения; в Node работает в фоне, в Python блокирует поток
bot.stop()bot.stop()прекращает получать сообщения

Ссылка на бота с меткой

Ссылка вида cloudgram.ru/имя_бота?start=метка открывает чат с ботом и показывает кнопку «Начать». Когда человек её нажимает, боту приходит команда /start метка — по метке видно, откуда он пришёл: из рекламы, по приглашению друга, с конкретной страницы сайта.

В самой переписке при этом ничего не появляется: человек нажал кнопку, а не написал команду. Метку видит только бот. Если в /start пришла метка — значит человек перешёл именно по вашей ссылке.
bot.onCommand("/start", function (msg) {
  var метка = msg.text.split(" ")[1] || "";

  if (метка) {
    msg.reply("Вы пришли по приглашению: " + метка);
  } else {
    msg.reply("Здравствуйте!");
  }
});
Метка — до 64 символов, пользователю она не показывается: он видит обычную кнопку «Начать». Годится для приглашений, привязки аккаунта к вашему сайту и подсчёта источников.
bot.onCallback(fn)@bot.on_callbackвызывает fn при нажатии callback-кнопки; у нажатия есть data, id, from и answer()
bot.getUpdates(opts)bot.get_updates()забирает обновления вручную; нужно, только если не используете start

Нажатия кнопок

Кнопка с полем callback не отправляет ничего в чат — вместо этого бот получает нажатие и отвечает всплывающей подсказкой.

bot.send(chatId, "Продолжить?", { buttons: [
  [{ text: "Да", callback: "yes" }, { text: "Нет", callback: "no" }]
]});

bot.onCallback((press) => {
  press.answer(press.data === "yes" ? "Поехали!" : "Отменено");
});
Node.jsPythonЧто делает
press.datapress.dataзначение callback нажатой кнопки
press.frompress.senderкто нажал
press.answer(text)press.answer(text)подсказка пользователю, до 200 символов
press.answer(text, {alert:true})press.answer(text, alert=True)то же, но окном, которое нужно закрыть

Отправка

Node.jsPythonЧто делает
bot.send(chatId, text)bot.send(chat_id, text)отправляет текст, до 4096 символов; возвращает сообщение
bot.send(chatId, text, {buttons})bot.send(chat_id, text, buttons=...)то же, но с кнопками под сообщением
bot.send(id, text, {format})bot.send(id, text, format=...)то же с разметкой: "html" или "markdown". См. разметку
bot.photo(chatId, файл)bot.photo(chat_id, файл)отправляет картинку: jpg, png, gif, webp
bot.video(chatId, файл)bot.video(chat_id, файл)отправляет видео mp4
bot.file(chatId, файл)bot.file(chat_id, файл)отправляет любой файл
bot.photo(id, файл, {caption})bot.photo(id, файл, caption=...)то же с подписью; подпись понимает format и buttons
bot.edit(chatId, msgId, text)bot.edit(chat_id, msg_id, text)меняет текст своего отправленного сообщения; принимает format и buttons
bot.delete(chatId, msgId)bot.delete(chat_id, msg_id)удаляет своё сообщение
bot.typing(chatId)bot.typing(chat_id)показывает пользователю «печатает…»; bot.typing(id, false) убирает
bot.stream(chatId, streamId, text)bot.stream(chat_id, stream_id, text)показывает текст, который ещё генерируется; один streamId обновляет тот же черновик
bot.stream(id, sid, text, {done:true})bot.stream(id, sid, text, done=True)помечает генерацию законченной
bot.getMe()bot.get_me()данные бота; заодно проверяет токен
bot.call(method, params)bot.call(method, params)вызывает метод API напрямую, если он новее модуля
msg.reply(text)msg.reply(text)отвечает в тот же чат, откуда пришло сообщение
Стриминг: вызывайте stream по мере генерации текста, затем send с готовым текстом — черновик исчезнет, сообщение останется в истории.

Файлы

Файл можно передать четырьмя способами, модуль сам разберётся: путь на диске, ссылка http/https, содержимое (Buffer или bytes) либо строка base64.

bot.photo(chatId, "./cat.jpg", { caption: "Котик" });
bot.video(chatId, "https://example.com/clip.mp4");
bot.file(chatId, "./отчёт.pdf", { name: "Отчёт за август.pdf" });
bot.photo(chat_id, "./cat.jpg", caption="Котик")
bot.video(chat_id, "https://example.com/clip.mp4")
bot.file(chat_id, "./отчёт.pdf", name="Отчёт за август.pdf")
ОграничениеЗначение
Размер файла25 МБ
Картинкиjpg, png, gif, webp
Видеоmp4
Подпись1024 символа
Тип определяется по самому файлу, а не по названию функции: если в bot.photo передать видео, придёт ошибка. Ссылки на внутренние адреса (localhost, 10.*, 192.168.*, 169.254.*) отклоняются.

Команды

Заданные команды всплывают подсказкой, когда пользователь начинает вводить «/», и открываются кнопкой слева в поле ввода.

bot.setCommands([
  { command: "start", description: "Меню и справка" },
  { command: "check", description: "Проверить каталог сейчас" }
]);
bot.set_commands([
    {"command": "start", "description": "Меню и справка"},
    {"command": "check", "description": "Проверить каталог сейчас"},
])
Имя команды — латиница, цифры и подчёркивания, до 32 символов, без слэша. Пустой список убирает подсказки.

Кто написал боту

По id из входящего сообщения можно получить данные пользователя — работает для тех, кто уже писал боту.

const who = await bot.getUser(msg.chat.id);
// { id, username, first_name, registered_at, registered_date, verified, avatar_url }
ПолеЧто содержит
idчисловой идентификатор
usernameюзернейм без @
first_nameимя
registered_atдата регистрации, строка
registered_dateта же дата в Unix time — удобно считать «сколько дней с нами»
verifiedподтверждённый аккаунт
avatar_urlссылка на аватарку, если есть

Мини-приложения

Ваш сайт открывается прямо в CloudGram: в поле ввода появляется кнопка, по ней всплывает окно с приложением. Сверху — название бота, крестик и меню с пунктами «На весь экран», «Перезагрузить», «Закрыть».

Node.jsPythonЧто делает
bot.setCommands(list)bot.set_commands(list)подсказки команд при вводе «/»: [{command, description}]
bot.getUser(chatId)bot.get_user(chat_id)данные пользователя: id, username, когда зарегистрировался
bot.setMenu(url, text)bot.set_menu(url, text)ставит кнопку мини-приложения; url обязательно https
bot.setMenu()bot.set_menu()убирает кнопку
bot.setMenu("https://example.com", "Открыть карту");

То же самое можно сделать без кода — командой в чате с ботом @newbot:

/setmenu @вашбот https://example.com Открыть карту

Кто открыл приложение

Адрес приложения дополняется данными о пользователе — во фрагменте, после решётки. Фрагмент не уходит на ваш сервер в запросе и не оседает в логах: приложение читает его само.

var p = new URLSearchParams(location.hash.replace(/^#/, ""));
var data = JSON.parse(p.get("cgInitData"));   // user, bot, auth_date
var signature = p.get("cgSignature");
ПолеЧто содержит
userкто открыл: id, first_name, username
botваш бот: id, username
auth_dateвремя открытия, Unix time
cgSignatureподпись данных
Данным из браузера верить нельзя — их легко подменить. Прежде чем показывать что-то личное, проверьте подпись на своём сервере: это HMAC-SHA256 от строки cgInitData с ключом — токеном вашего бота. Совпало — значит данные наши.
const crypto = require("crypto");
const ok = crypto.createHmac("sha256", ТОКЕН_БОТА).update(cgInitData).digest("hex") === cgSignature;

Команды из приложения

Приложение может управлять окном через postMessage:

parent.postMessage({ type: "cloudgram:close" }, "*");    // закрыть
parent.postMessage({ type: "cloudgram:expand" }, "*");   // на весь экран
Живой пример со всеми полями: miniapp-demo.html. Можно указать его в /setmenu и посмотреть, как всё работает.
Создавая бота или мини-приложение, вы принимаете правила для разработчиков: кто за что отвечает, какие данные вы получаете и что с ними можно делать.

Разметка текста

Параметр format у send и edit. Значение "html" — привычные теги, "markdown" — короткие символы. Без параметра текст уходит как есть.

HTMLMarkdownРезультат
<b>, <strong>**текст**жирный
<i>, <em>_текст_курсив
<u>, <ins>__текст__ или ++текст++подчёркнутый
<s>, <del>~~текст~~зачёркнутый
<code>, <pre>`текст`моноширинный
||текст||скрытый текст, открывается по клику
<h1>…<h6>заголовок, выглядит как жирный
<a href="адрес">текст</a>текст и адрес рядом: «текст (адрес)»
<br>перенос строкиновая строка
Ссылки в тексте подсвечиваются сами, писать теги для этого не нужно. Неизвестные теги отбрасываются, содержимое остаётся. Символы &lt;, &gt;, &amp; раскрываются в обычные.
bot.send(chatId, "<b>Готово</b>, файл: <code>отчёт.pdf</code>", { format: "html" });

Функции: аккаунт

Создание: cloudgram.account(clientId, clientSecret). Работает с вашим собственным аккаунтом.

Node.jsPythonЧто делает
account.me()account.me()данные вашего аккаунта
account.chats()account.chats()список ваших чатов
account.messages(chatId)account.messages(chat_id)последние сообщения чата, по умолчанию 50
account.messages(id, {limit})account.messages(id, limit=...)то же, сколько последних сообщений вернуть, до 200
account.send(chatId, text)account.send(chat_id, text)отправляет текст в чат
account.photo(chatId, файл, подпись)account.photo(chat_id, файл, caption)отправляет картинку: путь, ссылка или base64
account.video(chatId, файл, подпись)account.video(chat_id, файл, caption)отправляет видео (mp4)
account.file(chatId, файл, подпись)account.file(chat_id, файл, caption)отправляет документ без сжатия
account.edit(chatId, messageId, text)account.edit(chat_id, message_id, text)правит своё сообщение (текст уходит и в сам мессенджер)
account.delete(chatId, messageId)account.delete(chat_id, message_id)удаляет своё сообщение — у себя и на платформе (у всех)
account.read(chatId)account.read(chat_id)отмечает чат прочитанным (сбрасывает счётчик непрочитанных)
account.forward(toChatId, fromChatId, messageId)account.forward(to_chat_id, from_chat_id, message_id)пересылает сообщение в другой чат (пока только внутренние)
account.media(chatId, messageId)account.media(chat_id, message_id)скачивает вложение сообщения: Buffer в Node, bytes в Python
account.contacts()account.contacts()люди, с кем у аккаунта есть внутренний диалог
Функции отдают ровно то, что видно в CloudGram: удалённые и пустые сообщения не возвращаются, превью чата совпадает с последним сообщением в списке. У сообщений с вложением есть поле mediaType (photo, video, voice) и поле media — путь для скачивания файла через account.media(). Скачивание работает для внутренних чатов, Telegram и внешних ссылок (VK/Instagram); часть медиа отдельных каналов может быть недоступна.
Отправка работает во внутренние чаты CloudGram, Telegram, VK и Instagram. MAX пока принимает только текст: с вложением функция вернёт media_not_supported. Вложение можно передать путём к файлу, ссылкой или строкой base64 — модуль сам выберет способ.

Функции: сайт

Создание: cloudgram.site({ name, redirectUri }), в Python — cloudgram.site(redirect_uri, name). Пользователь один раз подтверждает доступ, пароль вам не передаётся.

Node.jsPythonЧто делает
site.link(state)site.link(state)возвращает адрес страницы согласия; state вернётся без изменений, сверьте его
site.connect(code)site.connect(code)меняет код из адреса возврата на подключение; код одноразовый и живёт 5 минут
site.checkRedirect()site.check_redirect()проверяет, что ваш адрес возврата разрешён

При отказе пользователя на адрес возврата придёт error=denied вместо code.

Подключение

Возвращает site.connect(). Сохранённый токен восстанавливается через cloudgram.session(token).

Node.jsPythonЧто делает
session.tokensession.tokenтокен подключения; храните на своём сервере
session.usersession.userкто выдал доступ
session.chats()session.chats()диалоги пользователя из всех мессенджеров
session.messages(chatId)session.messages(chat_id)сообщения диалога
session.send(chatId, text)session.send(chat_id, text)отправляет сообщение в диалог
session.revoke()session.revoke()отключает доступ; после этого токен перестаёт работать

Объекты

Сообщение

ПолеТипЧто содержит
message_idStringидентификатор сообщения
fromОбъектотправитель: id, is_bot, first_name, username
chatОбъектчат: id, type
dateIntegerвремя отправки, Unix time
textStringтекст сообщения
media_urlStringвложение, если есть; ссылка временная, скачивайте сразу

В Python у сообщения есть короткие свойства: msg.text, msg.chat_id, msg.sender, msg.sender_name, msg.media_url. Само сообщение при этом остаётся обычным словарём.

Кнопка

Кнопки передаются рядами: массив рядов, в каждом до 3 кнопок, всего до 6 рядов — лишние отбрасываются. Один ряд можно передать без вложенного массива. В url принимаются только адреса http и https.

ПолеТипЧто делает
textStringнадпись на кнопке, до 200 символов
urlStringоткрывает адрес при нажатии
sendBooleanотправляет надпись кнопки в чат как сообщение пользователя
copyStringкопирует указанный текст в буфер обмена
callbackStringбот получит нажатие с этим значением, до 64 символов; в чат ничего не отправляется
appStringоткрывает мини-приложение по адресу https; в углу кнопки — значок окошка
bot.send(chatId, "Выберите действие:", { buttons: [
  [{ text: "Каталог", app: "https://example.com/shop" }],
  [{ text: "Открыть сайт", url: "https://cloudgram.ru" }],
  [{ text: "/start", send: true }, { text: "Скопировать код", copy: "ABC-123" }]
]});

Чат

ПолеТипЧто содержит
idStringидентификатор чата; для личных совпадает с id пользователя
titleStringназвание чата
channelStringмессенджер: Telegram, VK, MAX, CloudGram
previewStringпоследнее сообщение
unreadIntegerсколько непрочитанных

Ошибки

При ошибке функция выбрасывает исключение с понятным текстом на русском. В Node это CloudGramError, в Python — cloudgram.CloudGramError.

ПолеЧто содержит
messageтекст ошибки от сервера
codeчисловой код: 401 — неверный ключ, 404 — нет доступа или неверный id, 429 — превышен лимит
errorмашинный код, например chat_not_found
try {
  await account.send(chatId, "текст");
} catch (e) {
  console.log(e.code, e.message);
}
try:
    account.send(chat_id, "текст")
except cloudgram.CloudGramError as e:
    print(e.code, e.message)

Лимиты

ЛимитЗначение
Запросы бота60 в минуту
Отправка сообщений ботом20 в минуту
Стриминг1200 в минуту
Запросы аккаунта60 в минуту на client_id
Длина сообщения4096 символов
Очередь обновлений200 на бота, хранится в памяти сервера
Ботов на пользователя10

При превышении функция выбрасывает ошибку с кодом 429 — повторите позже.