Bot API
Бот в Shalter — обычный аккаунт, которым управляет программа. Он состоит в чатах, пишет сообщения, показывает кнопки. Программу держите где угодно и пишите на чём угодно — через Bot API с токеном: встроенного редактора кода больше нет, бот работает с вашего сервера.
Все методы
53 методов. Все — по адресу /api/bot-api, все с заголовком
Authorization: Bearer …, все принимают и отдают JSON.
| Сообщения | |
|---|---|
POST /sendMessage | |
POST /sendPhoto | |
POST /sendDocument | |
POST /sendVideo | |
POST /sendVoice | |
POST /sendVideoNote | |
POST /sendSticker | |
POST /sendPoll | |
POST /sendLocation | |
POST /sendContact | |
POST /forwardMessage | |
POST /editMessageText | |
POST /editMessageKeyboard | |
POST /deleteMessage | |
POST /sendReaction | |
GET /getMessage | |
GET /getMessages | |
GET /searchMessages | |
GET /getPinnedMessages | |
POST /pinChatMessage | |
POST /unpinChatMessage | |
POST /sendChatAction | |
| Отложенная отправка | |
POST /scheduleMessage | |
GET /getScheduled | |
POST /cancelScheduled | |
| Чаты | |
GET /getChats | |
GET /getChat | |
POST /createGroup | |
POST /setChatTitle | |
POST /setChatDescription | |
POST /setChatPermissions | |
POST /exportChatInviteLink | |
POST /leaveChat | |
| Участники | |
GET /getChatAdmins | |
GET /getChatMember | |
GET /getChatMemberCount | |
POST /addChatMember | |
POST /promoteChatMember | |
POST /restrictChatMember | |
POST /banChatMember | |
POST /deleteAnyMessage | |
POST /sendGame | |
POST /getChatJoinRequests | |
POST /approveChatJoinRequest | |
POST /declineChatJoinRequest | |
| Люди | |
GET /resolveUsername | |
GET /getUser | |
GET /getUserStatus | |
GET /getCommonChats | |
| Каналы | |
POST /publishPost | |
GET /getChannelStats | |
| Бот | |
GET /me | |
GET /updates | |
GET /getMyCommands | |
POST /setMyCommands | |
POST /setMyProfile | |
GET /getMyStats | |
Два способа сделать бота
Свой сервер, свой язык
Скрипт на вашей машине спрашивает у Shalter новые сообщения и отвечает на них. Нужен только исходящий интернет — ни белого IP, ни домена, ни вебхуков. Любой язык, любые библиотеки, своя база данных. Это и есть HTTP API ниже.
Редактор внутри Shalter
Функция на JavaScript, которая исполняется на сервере при каждом входящем сообщении. Ни токена, ни хостинга, ни цикла опроса. Быстрее всего для небольшого бота — и заметно ограниченнее, о чём честно сказано в разделе про ограничения.
Токен
Настройки → Боты → «Создать бота». У бота сразу появляется собственный аккаунт с
юзернеймом вида @name_bot и токен — длинная строка, которая и есть право говорить от его
имени.
Потерять токен нестрашно: кнопка с ключом рядом с ботом показывает действующий в любой момент. Перевыпуск (кнопка обновления) выдаёт новый и делает старый недействительным — работающая программа со старым токеном сразу перестанет отвечать, так что перевыпускайте только если он утёк.
Быстрый старт
Проверить, что токен рабочий, — одна команда:
curl -H "Authorization: Bearer ВАШ_ТОКЕН" \
https://shalter.ru/api/bot-api/me
В ответ придёт аккаунт бота. Дальше — рабочий бот целиком, на голом Node без единой зависимости:
const TOKEN = process.env.SHALTER_TOKEN;
const API = "https://shalter.ru/api/bot-api";
const auth = { Authorization: `Bearer ${TOKEN}`, "Content-Type": "application/json" };
let after = new Date().toISOString(); // начинаем с «сейчас», старое не разгребаем
async function send(chatId, text) {
await fetch(`${API}/sendMessage`, {
method: "POST",
headers: auth,
body: JSON.stringify({ chatId, text }),
});
}
async function tick() {
const res = await fetch(`${API}/updates?after=${encodeURIComponent(after)}`, { headers: auth });
const { messages } = await res.json();
for (const msg of messages) {
after = msg.createdAt; // следующий опрос — строго после последнего разобранного
if (msg.text === "/start") await send(msg.chatId, "Привет! Я бот.");
else await send(msg.chatId, `Вы написали: ${msg.text}`);
}
}
setInterval(() => tick().catch(console.error), 2000);
Авторизация
Все запросы к /api/bot-api/* требуют заголовка:
Authorization: Bearer ВАШ_ТОКЕН
Без него — 401 с текстом Missing Authorization: Bearer <token>,
с неверным — 401 Invalid bot token. Куки и сессии здесь не используются: бот не человек,
он не «входит» в приложение.
Кто я
Возвращает аккаунт бота — имя, юзернейм, аватар. Нужен, чтобы проверить токен и узнать свой id.
{ "bot": { "id": "u_17…", "name": "Погодный", "username": "pogodnyy_bot", "isBot": true } }
Новые сообщения
Отдаёт сообщения из чатов бота, пришедшие после указанного времени. Свои собственные сообщения бот в ответе не увидит — иначе он отвечал бы сам себе по кругу.
| Параметр | Тип | Описание |
|---|---|---|
| after | строка, ISO 8601 | Время последнего разобранного сообщения. Без него отдаётся всё с начала времён. |
| timeout | число, секунды (до 50) | Длинный опрос: сервер не отвечает пустотой сразу, а держит запрос до первого сообщения. С ним бот отвечает мгновенно. |
| limit | число (до 200) | Сколько сообщений отдать за раз. По умолчанию 200. |
Сообщения отсортированы по возрастанию времени, поэтому следующее значение after —
это messages.at(-1).createdAt. За один запрос отдаётся не больше 200 сообщений:
разговорчивый бот не должен получить в ответ всю свою историю.
timeout — это и есть скорость бота.
Без него задержка ответа равна паузе в вашем цикле: спите две секунды между запросами — нажатая
кнопка молчит до двух секунд. С timeout=30 запрос висит до появления сообщения и
возвращается в тот же миг, когда оно пришло, а пустой ответ через 30 секунд означает «просто спроси
снова» — паузы между запросами не нужно вовсе.
let after = new Date().toISOString();
while (true) {
const res = await fetch(`${API}/updates?after=${after}&timeout=30`, { headers: auth });
const { messages } = await res.json();
for (const m of messages) await handle(m);
if (messages.length) after = messages.at(-1).createdAt;
}
Отправка
| Поле | Тип | Описание |
|---|---|---|
| chatId | строка | Обязательное. Куда писать — берётся из msg.chatId входящего сообщения. |
| text | строка | Обязательное. Пустой текст отклоняется с 400. |
| replyToId | строка | Ответить на конкретное сообщение — в чате будет видна цитата. |
| keyboard | массив рядов | Кнопки под сообщением, см. ниже. |
Бот может писать только в те чаты, где он состоит: попытка написать в чужой чат вернёт
404 Bot is not a member of this chat. Это не формальность — иначе токен любого бота
превращался бы в право рассылки по всему мессенджеру.
Бот пишет первым
sendMessage отправляет в чат, где бот уже состоит. Чтобы начать разговор самому — найти
человека и написать ему, — есть отдельный метод. Ради него боты обычно и заводятся: напомнить о
доставке, прислать код, сообщить о заказе.
await fetch(`${API}/sendMessageToUser`, {
method: "POST",
headers: auth,
body: JSON.stringify({
username: "ivan", // или userId: "u_…"
text: "Заказ №42 приедет к 18:00",
keyboard: [[{ text: "Отследить", data: "/track 42" }]],
}),
});
// → { chatId, message, user }
| Поле | Описание |
|---|---|
| username | @юзернейм получателя, с «@» или без. |
| userId | Идентификатор, если он у вас уже есть (например, из /updates). |
| text | Текст сообщения. |
| keyboard | Кнопки — как в обычной отправке. |
Отказы, которые надо уметь обрабатывать:
| Код | Когда |
|---|---|
| 403 | Получатель запретил ботам писать первыми (Настройки → Конфиденциальность) или заблокировал этого бота. |
| 404 | Такого @юзернейма нет. |
| 429 | Больше 20 новых разговоров в час. Отвечать в уже открытых чатах можно без ограничений. |
Картинки и файлы
Вложение передаётся ссылкой, а не загрузкой файла: у бота уже есть где хранить свои
картинки, а второй путь загрузки на сервер тянул бы за собой свои лимиты, чистку и квоты.
Годится и data:-URL, если файл маленький и генерируется на лету.
| Поле | Тип | Описание |
|---|---|---|
| chatId | строка | Обязательное. |
| url | строка | Обязательное. Адрес картинки или файла. |
| caption | строка | Подпись — она же становится текстом сообщения. |
| name | строка | Только для sendDocument: имя файла, которое увидит человек. |
| replyToId | строка | Ответить на сообщение. |
await fetch(`${API}/sendPhoto`, {
method: "POST", headers: auth,
body: JSON.stringify({ chatId, url: "https://example.com/chart.png", caption: "Отчёт за неделю" }),
});
Правка и удаление
Первый принимает messageId и новый text, второй — только
messageId. Изменения видны всем в чате сразу, без перезагрузки.
Бот может править и удалять только свои сообщения — чужое вернёт 403.
Это не формальность: токен бота живёт на чьём-то сервере, и право стирать чужие слова там ему не нужно.
const { message } = await (await fetch(`${API}/sendMessage`, {
method: "POST", headers: auth, body: JSON.stringify({ chatId, text: "Считаю…" }),
})).json();
const result = await heavyWork();
await fetch(`${API}/editMessageText`, {
method: "POST", headers: auth,
body: JSON.stringify({ messageId: message.id, text: `Готово: ${result}` }),
});
Закрепление
Принимает messageId и pinned (true по умолчанию,
false — открепить). Здесь правило другое, чем у правки: закрепить можно и чужое
сообщение, но только там, где бот администратор. Закреп видит весь чат — это
действие модератора, а не отправителя. В личном чате с ботом ограничения нет.
Печатает…
Принимает chatId. Показывает в чате, что бот думает над ответом — то, что отличает
занятого бота от зависшего. Живёт пять секунд, как и у людей: перед длинным шагом повторяйте,
а не ставьте один раз в начале.
Необязательный action — что именно происходит, те же значения, что в Telegram Bot API:
typing, choose_sticker, upload_photo, record_video,
upload_video, record_voice, upload_voice, upload_document,
record_video_note, upload_video_note. Без него — «печатает».
Чаты и люди
Все чаты, где состоит бот: id, тип, название, число участников и признак админа.
Без него единственный способ узнать свой чат — дождаться, пока туда кто-нибудь напишет.
Один чат подробно, вместе со списком участников.
Найти человека или канал по @юзернейму — то, чем бот ищет собеседника сам, а не ждёт, пока
напишут ему. Возвращает kind (user, channel, group)
и найденное.
Кто написал. В сообщении приходит только senderId; отсюда берутся имя и юзернейм,
чтобы ответить «Спасибо, Аня», а не «Спасибо, u_1786…». Чужой адрес почты не отдаётся никогда —
ни здесь, ни где-либо ещё.
История чата
Страница сообщений, свежие в конце. before — идентификатор сообщения, от которого идти
назад; в ответе есть hasMore. За раз не больше 200. Читаются только чаты, где бот состоит.
Стикеры, опросы, реакции
chatId, emoji, необязательные name и scene
(имя нарисованной сцены — см. набор «Shalter» в приложении).
chatId, question и options — от двух до восьми вариантов.
Голосуют люди, бот только создаёт.
messageId и emoji. Повторный вызов с тем же эмодзи снимает реакцию —
как и нажатие пальцем.
Права администратора
Всё в этом разделе требует, чтобы бота назначили администратором чата. Это то же правило, что и для человека: права даёт владелец чата, а не наличие токена.
Удалить чужое сообщение — то, ради чего заводят бота-модератора. Своё бот удаляет методом
deleteMessage и без прав.
chatId, userId, minutes (до 30 дней). Временное молчание:
человек остаётся в чате и видит переписку.
Убрать участника из чата.
chatId. Заявки на вступление в канал или группу с включёнными «Заявками на вступление»:
userId, name, username, bio, createdAt.
chatId, userId. Принять или отклонить заявку — человек сразу увидит канал в списке
или уведомление об отказе.
Уйти из чата — единственное здесь, что боту не нужно согласовывать.
400. Иначе одним утёкшим токеном можно
было бы обезглавить чат.
Видео, голос, место, контакт
Те же поля, что у sendPhoto: chatId, url, caption,
replyToId. Файл передаётся ссылкой — см. раздел «Картинки и файлы».
lat и lng — точка на карте.
name, phone, необязательный userId.
Работа с сообщениями
messageId и toChatId — оба чата должны быть чатами бота. В пересланном
видно, откуда и от кого оно.
Поиск внутри одного чата, до 50 последних совпадений.
Заменить кнопки под своим сообщением, не трогая текст — так бот обновляет клавиатуру после нажатия.
Отложенная отправка
sendAt — время в ISO 8601, обязательно будущее. Нужно затем же, зачем людям: разослать
объявление в назначенный час, а не будить программу ночью.
Участники и права
title и memberIds. Бот становится владельцем созданной группы.
memberIds проходят лишь те, с кем бот уже состоит в общем чате — иначе токен
превращался бы в право затащить любого человека в любую группу.
admin: true|false. Владельца изменить нельзя — он не «ещё один администратор».
revoke: true выпускает новую ссылку вместо старой.
Отдаёт человека, его права и срок ограничения, если он есть.
Общие чаты бота с этим человеком.
Каналы
Подписчики, посты, просмотры и комментарии. И то и другое — только для канала, где бот администратор.
Канал и его обсуждение
Связка «канал + группа комментариев» собирается скриптом целиком: завести канал, прицепить к нему обсуждение, опубликовать пост. Раньше это можно было сделать только руками через меню.
const { chat } = await (await fetch(`${API}/createChannel`, {
method: "POST",
headers: auth,
body: JSON.stringify({
title: "Пиццерия · новости",
description: "Акции и новинки",
username: "pizza_news", // необязательно
isPublic: true, // работает только вместе с username
}),
})).json();
Владельцем канала становится не только бот, но и его хозяин: иначе канал, созданный программой, принадлежал бы программе — сменился токен, и живой человек остался бы без доступа к собственному каналу.
| action | Что делает |
|---|---|
create | Заводит новую группу-обсуждение и сразу цепляет её к каналу. Можно передать свой title. |
link | Цепляет уже существующую группу — по groupId. Бот должен быть администратором и канала, и группы. |
unlink | Отцепляет обсуждение. Сама группа остаётся: у неё свои участники и своя переписка. |
// Канал → обсуждение → первый пост, одним скриптом
const ch = (await post("createChannel", { title: "Пиццерия · новости" })).chat;
const d = (await post("setChatDiscussion", { channelId: ch.id, action: "create" })).discussion;
await post("publishPost", { chatId: ch.id, text: "Открылись! Первая пицца — в подарок." });
// комментарии к посту прилетят в d.id обычными сообщениями через /updates
Что сейчас прицеплено к каналу — чтобы скрипт мог проверить, а не гадать.
Профиль бота
name и description — как бот представляется. Удобно править вместе с
выкладкой новой версии, а не руками в настройках.
Сколько у бота чатов, сколько он отправил сообщений и со сколькими людьми пересекается.
Кнопки
keyboard — массив рядов, каждый ряд — массив кнопок. Нажатие отправляет боту обычное
сообщение с текстом из data, так что обрабатывается оно там же, где и всё остальное.
Поле можно назвать и action — сервер понимает обе формы.
await fetch(`${API}/sendMessage`, {
method: "POST",
headers: auth,
body: JSON.stringify({
chatId,
text: "Что показать?",
keyboard: [
[{ text: "Погода", data: "/погода" }, { text: "Курс", data: "/курс" }],
[{ text: "Помощь", data: "/help" }],
// Кнопка, открывающая мини-приложение бота, а не отправляющая ответ:
[{ text: "Открыть магазин", app: "https://example.com/app/shop" }],
// Ссылка и цветная кнопка:
[{ text: "Сайт", url: "https://example.com" }, { text: "Купить", data: "/buy", style: "success" }],
],
}),
});
Кнопка с полем app открывает мини-приложение бота — адрес должен
вести на тот же сайт, что указан в настройках приложения, иначе кнопка ничего не откроет.
Кнопка с полем url (только http/https) открывает ссылку. Поле style красит
кнопку: primary, success или danger.
Альбомы, кружки и копирование
Всё, что умеет показывать приложение, теперь умеет отправлять и бот. Файлы — ссылкой: своё хранилище у бота уже есть, а второй путь загрузки тянул бы за собой свои лимиты и чистку.
| Метод | Поля | Что делает |
|---|---|---|
sendVideo | chatId, url, caption?, replyToId? | Видео с проигрывателем. |
sendVoice | chatId, url, durationSec?, caption? | Голосовое сообщение. Без durationSec шкала будет пустой. |
sendVideoNote | chatId, url, durationSec? | Круглое видеосообщение. |
sendMediaGroup | chatId, items[], caption? | Альбом до 10 файлов одним сообщением: [{ kind: "image", url }, …]. Виды: image, video, file, voice, video-note. |
copyMessage | chatId, messageId | Отправить содержимое чужого сообщения от своего имени, без пометки «переслано». Копировать можно только из чатов, где бот состоит. |
setMyAvatar | image | Картинка бота — ссылкой или data:-строкой. Имя и описание меняет setMyProfile. |
await fetch(`${API}/sendMediaGroup`, {
method: "POST",
headers: auth,
body: JSON.stringify({
chatId,
caption: "Новинки недели",
items: [
{ kind: "image", url: "https://example.com/1.jpg" },
{ kind: "image", url: "https://example.com/2.jpg" },
{ kind: "video", url: "https://example.com/3.mp4" },
],
}),
});
Мини-приложения
Мини-приложение — обычная веб-страница на вашем сервере, которая открывается прямо внутри Shalter, поверх переписки, и знает, кто её открыл. Это способ дать боту настоящий интерфейс: каталог, корзину, форму, игру — всё, для чего кнопок и текста мало.
Как включить
Настройки → Боты → ваш бот → Настройки бота → «Мини-приложение»: адрес страницы и надпись на кнопке. Или из кода:
await fetch(`${API}/setWebApp`, {
method: "POST",
headers: auth,
body: JSON.stringify({ url: "https://example.com/app", name: "Магазин" }),
});
Только https (http разрешён для localhost — на нём и
отлаживают). Пустой url убирает приложение. После этого кнопка появляется в шапке чата
с ботом, и её же можно поставить в клавиатуру сообщения.
На странице
<script src="https://ваш-shalter/js/shalter-web-app.js"></script>
<script>
const app = window.Shalter.WebApp;
app.ready(); // убрать индикатор загрузки
document.title = "Привет, " + app.user.name; // кто открыл (без проверки — для вида)
app.MainButton.setText("Оформить заказ").show();
app.MainButton.onClick(async () => {
app.MainButton.showProgress();
await app.sendData(JSON.stringify({ items: 2 })); // уйдёт боту, окно закроется
});
</script>
| Что есть | Описание |
|---|---|
initData | Подписанная строка — её и надо переслать боту для проверки. Целиком, не по частям. |
user | Кто открыл: id, name, username, isPremium. Удобно для интерфейса, недостаточно для доверия. |
colorScheme | "light" или "dark" — тема, в которой человек сидит. |
ready() | Сообщить, что страница отрисовалась. |
close() | Закрыть окно. |
sendData(данные) | Отправить данные боту и закрыть окно. До 4096 символов. |
openLink(url) | Открыть внешнюю ссылку в настоящем браузере. |
showAlert / showConfirm | Сообщение и вопрос «да/нет»; showConfirm возвращает промис с true/false. |
MainButton | Нижняя кнопка на всю ширину: setText, show, hide, enable, disable, showProgress, hideProgress, onClick. |
sendData устроен честнее, чем в Telegram.
Там такое сообщение видит только бот, у человека в переписке не появляется ничего. Здесь это обычное
сообщение от самого человека — он видит в чате, что именно отправило приложение от его имени, а бот
получает его тем же /updates, что и всё остальное.
Приложение без своего сервера
Хостинга может не быть вовсе — тогда страницу хранит и раздаёт сам Shalter. Присылаете HTML,
получаете работающее приложение по адресу /app/<юзернейм бота>; скрипт
shalter-web-app.js подставляется автоматически.
await fetch(`${API}/setWebAppCode`, {
method: "POST",
headers: auth,
body: JSON.stringify({
name: "Магазин",
code: `
<h1>Пиццерия</h1>
<button id="b">Заказать</button>
<script>
const app = Shalter.WebApp;
app.ready();
document.getElementById("b").onclick = () => app.sendData("заказ: 1 пицца");
</script>`,
}),
});
Можно прислать и целый документ с <html>, и один фрагмент разметки — во втором
случае Shalter обернёт его сам. Пустой code убирает приложение. То же самое поле есть в
приложении: Настройки → Боты → Настройки бота → «Мини-приложение» → «Написать здесь».
/api от имени открывшего, ни общего с приложением хранилища
(Content-Security-Policy: sandbox). Всё, что ей нужно, она получает через
Shalter.WebApp — как и приложение на чужом домене. Свои запросы к своему серверу делать
можно: обычный fetch по абсолютному адресу с CORS.
Проверка, кто открыл
initData — строка вида user=…&chat_id=…&bot_id=…&auth_date=…&hash=….
Всё, кроме hash, подписано ключом, который знают только Shalter и ваш токен, поэтому
подделать имя нельзя, а поверить содержимому без проверки — можно только зря. Алгоритм тот же, что в
Telegram Mini Apps:
import crypto from "node:crypto";
function checkInitData(initData, token) {
const params = new URLSearchParams(initData);
const hash = params.get("hash");
const check = [...params.entries()]
.filter(([k]) => k !== "hash")
.sort(([a], [b]) => (a < b ? -1 : 1))
.map(([k, v]) => `${k}=${v}`)
.join("\n");
const secret = crypto.createHmac("sha256", "ShalterWebAppData").update(token).digest();
const mine = crypto.createHmac("sha256", secret).update(check).digest("hex");
if (mine !== hash) return null;
return JSON.parse(params.get("user"));
}
Не хотите считать сами — отдайте строку серверу:
const res = await fetch(`${API}/checkWebAppData`, {
method: "POST",
headers: auth,
body: JSON.stringify({ initData }),
});
const { ok, user, chatId } = await res.json(); // ok: false — подпись не сошлась
initData, подделает и её
результат. Подпись действительна сутки — старую, подсмотренную в чужой истории браузера, сервер
отвергнет.
Команды
Список команд задаётся в приложении (Настройки → Боты → иконка списка) в формате BotFather — по одной на строку:
start - Начать
погода - Прогноз на сегодня
help - Что я умею
Он показывается людям по кнопке «/» в чате с ботом. Сами команды приходят обычными сообщениями
с текстом /погода — отдельного типа события для них нет.
То же самое из программы — удобно обновлять список вместе с выкладкой новой версии бота, а не руками. Ведущий слэш можно не писать, он всё равно срезается.
await fetch(`${API}/setMyCommands`, {
method: "POST", headers: auth,
body: JSON.stringify({ commands: [
{ command: "start", description: "Начать" },
{ command: "погода", description: "Прогноз на сегодня" },
]}),
});
Редактор внутри приложения
Настройки → Боты → иконка кода. Внутри — настоящий редактор (подсветка синтаксиса, автодополнение по Bot API, парные скобки, поиск, отмена), кнопка «Запустить» для проверки на тестовом сообщении и консоль с логами вашего бота.
Код исполняется на сервере, при каждом входящем сообщении. Токен, хостинг и цикл опроса не нужны вовсе.
handleMessage
Единственное требование к коду — определить эту функцию:
async function handleMessage(msg, bot) {
if (msg.text === "/start") {
return bot.send("Привет! Напишите что-нибудь.");
}
// Можно ходить в чужие API прямо отсюда
const res = await fetch("https://api.example.com/weather?city=Moscow");
const data = await res.json();
await bot.send(`Сейчас ${data.temp}°`, {
keyboard: [[{ text: "Обновить", data: "/погода" }]],
});
}
| Что доступно | Описание |
|---|---|
| msg | Входящее сообщение: id, chatId, senderId, text, createdAt. |
| bot.send(text, opts) | Ответить в тот же чат. opts — те же keyboard и replyToId. |
| bot.sendTo(chatId, text, opts) | Написать в другой чат, где бот состоит. |
| msg.type === "join_request" | Новая заявка на вступление в канал/группу, где бот — админ: chatId, userId, userName, username, bio. |
| bot.approveJoin(userId, chatId?) / bot.declineJoin(userId, chatId?) | Принять или отклонить заявку (по умолчанию — в чате из msg). |
| bot.joinRequests(chatId?) | Список ожидающих заявок. |
| fetch | Обычный fetch — запросы к внешним сервисам. |
| console.log / warn / error | Пишет в консоль редактора и в журнал бота. |
| setTimeout / clearTimeout | Задержки внутри обработчика. |
Ограничения — читать до того, как писать
Поверхность намеренно узкая: require, process, файловая система, модули npm
и глобальные объекты Node недоступны.
- Состояние не сохраняется. Каждый вызов начинается с чистого листа: переменные не переживают следующее сообщение. Нужна память — храните её во внешнем сервисе через
fetch, или пишите бота на своём сервере. - 300 мс непрерывной работы процессора. Ожидание (fetch, отправка) сюда не считается — у него свой потолок в 20 секунд на весь вызов. Ограничение жёсткое по неприятной причине: сервер однопоточный, и пока ваш синхронный код крутится, приложение не отвечает никому.
- 20 секунд на вызов целиком. Медленный внешний API успеет ответить, зависший — нет.
vm сказано прямо: это не средство безопасности. Здесь он
ограничивает случайный ущерб от вашего собственного кода — опечатки, бесконечного цикла,
забытого await. Не вставляйте туда скрипт от незнакомца: это ровно то же самое, что запустить у себя
чужой шелл-скрипт.
Писать в VS Code
Встроенный редактор — это редактор одного файла, а не среда разработки: без дерева проекта, отладчика, npm-пакетов, git и расширений. Если нужно всё это — пишите бота у себя в VS Code как обычную программу и подключайте его по HTTP API из первой половины страницы. Тогда доступно ровно всё, к чему вы привыкли: любые зависимости, точки останова, тесты, своя база, деплой куда угодно.
Разница только в том, где живёт программа. Формат сообщений и отправка одинаковые: bot.send()
в редакторе и POST /sendMessage снаружи — это один и тот же вызов внутри.
Ошибки
| Код | Когда |
|---|---|
| 400 | Пустой text в sendMessage. |
| 401 | Нет заголовка Authorization или токен недействителен. |
| 404 | Бот не состоит в этом чате либо чат не существует. |
| 429 | Слишком часто. Опрос раз в 1–2 секунды в лимиты укладывается с запасом. |
Ошибка внутри кода в редакторе не роняет ни сервер, ни бота: она попадает в журнал бота (Настройки → Боты → код → консоль), а следующее сообщение обрабатывается как ни в чём не бывало.
Объекты
Message
| Поле | Тип | Описание |
|---|---|---|
| id | строка | Идентификатор сообщения — годится для replyToId. |
| chatId | строка | Чат, куда отвечать. |
| senderId | строка | Кто написал. |
| text | строка | Текст. У сообщения с вложением может быть пустым. |
| createdAt | строка, ISO 8601 | Время. Оно же — следующее значение after. |
Кнопка
| Поле | Тип | Описание |
|---|---|---|
| text | строка | Что написано на кнопке. |
| data | строка | Что придёт боту сообщением при нажатии. |
Игры: SDK внутри игры
Полная инструкция по играм — на отдельной странице: shalter.ru/games.
Игра в Shalter — одна HTML-страница (меню «Игры» → «Мои игры» → «Создать игру»). Она запускается в
изолированном окне без доступа к аккаунту и сайту, а с приложением общается через объект
window.Shalter — его вставлять не нужно, он уже есть.
// очки — по ходу игры и в конце (рекорд сохранится сам)
Shalter.score(points);
Shalter.gameOver(points);
// кто играет и какая тема у приложения
console.log(Shalter.player.name, Shalter.theme.dark, Shalter.theme.accent);
// рекорды: лучший результат игрока и таблица (в чате, откуда открыли игру)
const best = await Shalter.getBest();
const top = await Shalter.getLeaderboard({ limit: 10 }); // [{ position, name, score, isMe }]
Shalter.share(); // отправить «мой результат — N» с карточкой игры в чат
Shalter.vibrate(40); // вибрация на телефоне
Shalter.close(); // закрыть игру
| Что | Описание |
|---|---|
| Shalter.score(n) / gameOver(n) | Текущий и итоговый счёт. Сохраняется лучший результат — общий и в чате, где отправили карточку игры. |
| Shalter.player | { id, name } игрока. |
| Shalter.theme / Shalter.chat | { dark, accent } — тема приложения; { id } — чат, откуда открыли (или null). |
| await Shalter.getBest() | Лучший результат игрока. |
| await Shalter.getLeaderboard({ limit, chat }) | Таблица рекордов; chat: false — общая, а не этого чата. |
| Shalter.share(text?) | Отправить результат в чат вместе с карточкой игры. |
| Shalter.vibrate(ms), Shalter.close() | Вибрация и закрытие игры. |
| localStorage | Работает как обычно — данные хранятся отдельно для каждой игры. |
Game API
HTTP-методы для разработчика игры: обновлять код из своего репозитория или CI, смотреть статистику и ставить рекорды со своего сервера — так счёт нельзя накрутить в браузере. Токен — в редакторе игры, кнопка «🔑 API-токен» (показывается один раз; новый отзывает прежний).
curl -H "Authorization: Bearer game_ВАШ_ТОКЕН" https://shalter.ru/api/game-api/getStats
curl -X POST -H "Authorization: Bearer game_ВАШ_ТОКЕН" -H "Content-Type: application/json" \
-d '{"userId":"u_…","score":1500}' https://shalter.ru/api/game-api/setScore
| Метод | Параметры | Что делает |
|---|---|---|
GET /getGame | — | Игра и статистика. |
POST /updateGame | code, title, description, emoji, isPublic | Обновить игру (переданные поля). Код — до 512 КБ. |
GET /getStats | — | { plays, players, topScore }. |
GET /getScores | chatId?, limit? | Таблица рекордов (общая или чата). |
POST /setScore | userId, score, chatId?, force? | Записать результат. Без force — только если лучше прежнего; с force — как есть (сброс накрутки). |
POST /deleteScore | userId | Удалить результаты игрока. |
Бот может отправить карточку игры в чат: POST /api/bot-api/sendGame с chatId и gameId
(встроенные игры: builtin-2048, builtin-snake, builtin-flappy).
Адрес API на этом развёртывании — /api/bot-api от корня сайта.
Вопросы и заявки на интеграцию — через страницу для партнёров.