В сообщение Telegram теперь можно засунуть небольшую веб-страницу со своим JavaScript. Получатель открывает чат и сразу может с ней взаимодействовать: нажимать кнопки, двигать элементы, играть. Код выполняется прямо внутри сообщения, а HTML и стили бот отправляет вместе с ним.
Это новые HTML-виджеты. На весь код дают всего 4 КБ после сжатия, и мне стало интересно, что в них вообще поместится. Оказалось, даже игра.
Для эксперимента я собрал башню: нажимаешь на сообщение, очередной этаж падает вниз. Если поставил криво, торчащий кусок отваливается. Отправил её себе в Telegram, потыкал. Вся игра заняла чуть больше 3 КБ после сжатия.
Игра с башней работает внутри сообщения Telegram
Бот может прислать новые данные в уже отправленный виджет. Скрипт получит их и обновит текст или другие элементы сообщения, если заранее написать для этого обработчик.
Одна пиццерия, три интерфейса
Давайте возьмём классический пример: пиццерия принимает заказы через Telegram. Банально, зато на нём удобно сравнить обычные сообщения бота, Mini App и новые виджеты.
Покупатель выбрал «Маргариту», решил взять две и хочет увидеть итоговую сумму.
Сообщение бота
Кнопка «Добавить в заказ» стоит под сообщением. Когда на неё нажимают, бот получает событие. Если нужно показать новую сумму, бот редактирует сообщение или присылает следующее.
Сообщение бота с ценой и кнопкой «Добавить в заказ»Mini App
Mini App по сути открывает ваш сайт во встроенном браузере Telegram, на отдельном экране. Вы размещаете страницу на сервере, а Telegram загружает её по URL. Для нашей пиццерии это может быть каталог с корзиной и формой доставки.
У Mini Apps есть API для интеграции с Telegram: можно получить данные пользователя, подстроить цвета под тему мессенджера или использовать его кнопки. Сам интерфейс вы пишете на привычных HTML, CSS и JavaScript.
Каталог пиццерии в Mini App с переходом в корзинуHTML-виджет
Жмёте «+», и сумма меняется прямо в карточке. Её пересчитывает скрипт внутри сообщения. Когда нажмёте «Оформить заказ», виджет обратится к боту, чтобы тот записал заказ на сервере.
HTML-виджет в сообщении с выбором количества и пересчётом суммы
Как меняется статус в уже отправленной карточке
Обновление с сервера я пока не пробовал, поэтому этот пример разобрал по документации Telegram. Допустим, покупатель оформил заказ. Бэкенд пиццерии сохранил его под номером 42 со статусом «Принят», и теперь бот отправляет покупателю карточку.
Карточка заказа
Карточка хранится в файле order-widget.html. В разметке есть поля для номера и статуса заказа. JavaScript внутри заполняет эти поля при открытии виджета и обновляет их, когда бот присылает новые данные.
Заказ №…
Статус: Загрузка…
const widget = window.TelegramWidget; const orderId = document.getElementById('order-id'); const orderStatus = document.getElementById('order-status'); function renderOrder() { try { const order = JSON.parse(widget.chatData || 'null'); orderId.textContent = order?.id ?? '—'; orderStatus.textContent = order?.status ?? 'Нет данных'; } catch { orderStatus.textContent = 'Ошибка данных'; } } widget.onEvent('chatDataChanged', renderOrder); renderOrder();
Объект window.TelegramWidget предоставляет Telegram. Его свойство chatData содержит строку с данными этого сообщения. Функция renderOrder() разбирает JSON и записывает номер заказа в order-id, а статус в order-status. В конце скрипт подписывается на событие chatDataChanged и вызывает renderOrder() первый раз, чтобы сразу показать заказ.
Бот отправляет карточку покупателю
В нашем примере бэкенд написан на Node.js. Он читает HTML из файла, а токен бота из переменной окружения TELEGRAM_BOT_TOKEN. Покупатель общается с ботом в личном чате с условным ID 123456789. Его заказ №42 уже принят.
import { readFile } from 'node:fs/promises';const token = process.env.TELEGRAM_BOT_TOKEN;const html = await readFile('./order-widget.html', 'utf8');async function callTelegram(method, params) { const response = await fetch( `https://api.telegram.org/bot${token}/${method}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(params), }, ); const data = await response.json(); if (!response.ok || !data.ok) { throw new Error(data.description || 'Ошибка Telegram API'); } return data.result;}async function sendOrderWidget(chatId, order) { return callTelegram('sendWidget', { chat_id: chatId, html, chat_data: JSON.stringify(order), });}// Пример заказа, который бэкенд уже принял.const order = { id: 42, status: 'Принят' };const chatId = 123456789; // Условный ID чата покупателя.const message = await sendOrderWidget(chatId, order);
Функция sendOrderWidget() отправляет в Telegram разметку в поле html и строку {"id":42,"status":"Принят"} в поле chat_data. Статус «Принят» задан в объекте order на сервере. JavaScript в виджете прочитает его через widget.chatData и покажет покупателю.
Telegram возвращает отправленное сообщение, и код сохраняет его в message. Поля message.chat.id и message.message_id указывают, где находится карточка заказа №42. По ним бот найдёт её при обновлении статуса.
Кухня начала готовить
Кухня взяла заказ в работу. Бэкенд меняет статус в order на «Готовится» и вызывает editMessageWidgetData через функцию callTelegram. ID чата и сообщения он берёт из сохранённого message.
// Позже, когда кухня взяла этот заказ в работу.order.status = 'Готовится';await callTelegram('editMessageWidgetData', { chat_id: message.chat.id, message_id: message.message_id, chat_data: JSON.stringify(order),});
Telegram обновляет chatData у выбранного сообщения и вызывает событие chatDataChanged. Наш JavaScript снова запускает renderOrder(), и надпись меняется на «Готовится». HTML-файл повторно отправлять не нужно. Чтобы изменить сам код виджета, придётся прислать новый.
Какие ограничения есть у виджетов
Вся разметка, стили и JavaScript должны уложиться в 32 768 байт исходника и 4 096 байт после сжатия. Поэтому к карточке пиццы уже не так просто добавить фотографии. Загрузить их по URL нельзя, а встроенные в HTML изображения тоже занимают место в этих четырёх килобайтах.
JavaScript выполняется в QuickJS без доступа к сети. Например, скрипт карточки не сможет сам запросить актуальную цену через fetch. За внешними данными ему придётся обращаться к боту. Это повлияет на то, какую логику пиццерия сможет перенести в сообщение.
С вводом адреса доставки тоже возникнет проблема. Telegram удаляет из разметки input, textarea и select, а клавиатура внутри виджета недоступна. Количество пицц можно выбрать кнопками, но для ввода улицы и номера квартиры понадобится другой интерфейс. В нашем примере форму доставки можно оставить в Mini App, а в переписке показывать карточку заказа со статусом.
Где это можно попробовать
Когда башня заработала, я попытался переслать её в канал. Telegram не дал. В публичном Bot API виджеты пока работают в личке с ботом, а пересылка отключена. Если хотите потыкать свой HTML в чате, отправьте его себе через sendWidget. Подписчикам канала пока можно показать запись или дать ссылку на бота.
Мне не терпится увидеть, что люди с этим сделают. Уже хочется открывать чужие эксперименты, тыкать во всё подряд и разбираться, как это вообще уместили в одном сообщении.
Документация по виджетам · Метод sendWidget в Bot API
Источник: habr.com