Виджеты в сообщениях Telegram получили обратный канал к боту. В бета-сборках Telegram для macOS 13.1.0 от 26 октября у скрипта виджета появился вызов request(): по нажатию он отправляет боту до 64 байт и ждёт ответ до 1 024 байт. Без нажатия запрос уходит только в личном чате с ботом, если бот разрешил такие запросы, пока виджет на экране и не чаще раза в пять секунд. Там же виджет теперь знает, кто его открыл, — id, имя, юзернейм и наличие Premium, — и в каком типе чата он лежит. Сами виджеты замечены в бетах iOS, macOS и Android, но эту механику пока видно только в сборке для macOS; в публичной схеме API и в Bot API виджетов нет.

Пост в Telegram — @gramnews
Какими будут сообщения-виджеты

Что изменилось между 25 и 26 октября

Бета macOS раздаётся через фид обновлений, каждая сборка — zip с приложением. Для сравнения взяты 13.1.0.283381 от 25 октября и 13.1.0.283391 от 26 октября.

Скрипт виджета общается с клиентом через глобальный объект TelegramWidget. Его код лежит в пакете WidgetSandbox внутри приложения, в файле sandbox-bundle.js. В 283381 объект умел немного: отдать тему оформления, подписаться на события, открыть ссылку и включить вибрацию. В 283391 к этому добавились:

ЧтоЧто делает
request(data)отправляет боту строку от 1 до 64 байт и возвращает его ответ
viewerid, имя, фамилия, юзернейм и признак Premium того, кто смотрит
chatTypeтип чата: личный, группа, супергруппа или канал
chatDataданные, привязанные к сообщению в этом чате, до 1 024 байт
publicDataданные самого сообщения, до 1 024 байт
watchPublicData()подписка на изменения publicData
isForward и isPreviewпересланная ли это копия и не превью ли это ещё не отправленного инлайн-результата
copyText(text)кладёт в буфер обмена до 256 знаков
setResizeAnchorкрай, от которого виджет растёт при смене высоты, — верхний или нижний

К трём прежним событиям — смена темы, активация и деактивация — добавились chatDataChanged и publicDataChanged.

Одновременно из пакета HTMLBubbles убрали всё демонстрационное: тринадцать демо-переписок, о которых шла речь в материале от 13 октября, папку ported с перенесёнными карточками, многопользовательскую игру voxel-clash и папку widget-sandbox с примерами. В пакете осталась одна библиотека three.js.

Когда виджет может написать боту

Правила записаны в той же песочнице, текстом ошибок, которые получает скрипт, и в строках приложения:

  • запрос уходит из обработчика нажатия на виджет, один вызов на одно нажатие;
  • без нажатия — только в личном чате с ботом, только если сообщению разрешены фоновые запросы (флаг background_query), только пока виджет на экране и не чаще раза в пять секунд;
  • пока ждёт ответа прошлый запрос, новый не отправляется;
  • превью инлайн-результата, которое ещё не отправлено в чат, бота не спрашивает;
  • запросы выключены, если бот не разрешил их этому сообщению, — у пересланной копии отдельное разрешение;
  • ответ бота — только текст и не длиннее 1 024 байт, иначе скрипт получает ошибку INVALID_ANSWER; не ответил вовремя — TIMEOUT.

У пересланной копии chatData пуст, а publicData остаётся.

В строках приложения рядом с кодом HTML-пузыря появилась фраза «A tap may send data to the bot» — «нажатие может отправить данные боту». Где именно клиент её показывает, по сборке не видно.

Что делать

  • Виджеты пока живут только в бетах, и от ботов их не прислать: в Bot API нет метода, который отправлял бы виджет.
  • Нажимая на виджет, учитывайте, что каждое нажатие может стать запросом к боту. Скрипт видит ваш id, имя, юзернейм и Premium, а id занимает до 16 цифр — в запрос в 64 байта он помещается.
  • Сам по себе, без нажатия, виджет пишет боту только в личном чате с этим ботом. Для групп и каналов песочница фоновых запросов не предусматривает.
  • Сети у виджета нет: fetch, XMLHttpRequest, WebSocket и воркеры в песочнице заменены заглушками, которые возвращают ошибку. Наружу данные уходят через бота или через ссылку, которую виджет открывает (openLink принимает только https и не длиннее 256 знаков), — так было и в сборке 25 октября.

Как сервер доставит виджет

В бинарнике 283391 появились имена типов схемы, которых не было в 283381. В публичной схеме слоя 232 — ни у Telegram Desktop, ни у Telegram Web A — их нет, нет виджетов и в документации Bot API. Устройство читается по именам, поэтому дальше — прочтение, а не описание:

  • botInlineMessageWidget, inputBotInlineMessageWidget и inputBotInlineMessageWidgetId — бот, судя по всему, отдаёт виджет как инлайн-результат, целиком или по id;
  • inputMediaWidgetId — виджет как вложение сообщения по id;
  • updateBotWidgetQuery, updateInlineBotWidgetQuery и updateBotWidgetCopyQuery — запросы к боту от виджета в обычном сообщении, в отправленном через инлайн-режим и, судя по имени, в пересланной копии; ответ — botWidgetQueryAnswer;
  • updateMessageWidgetData и widgetPublicData — сервер присылает новые данные уже отправленного виджета, а в песочнице на это есть событие publicDataChanged;
  • ошибки сервера WIDGET_QUERY_NOT_ALLOWED и WIDGET_RESPONSE_TIMEOUT выглядят парой к ошибкам скрипта NOT_ALLOWED и TIMEOUT.

Строки клиента называют и флаги разрешений: allow_queries у сообщения, fwd_allow_queries у его пересланных копий и background_query для запросов без нажатия. Что именно бот получает вместе с запросом — id нажавшего, как у кнопок под сообщением, или нет, — по сборке не видно.

Что проверяют стенды в открытом коде

В открытом репозитории клиента движок виджетов подключён подмодулем twidget; по ссылке подмодуля GitHub отвечает 404 — репозиторий закрыт или удалён. Рядом лежат стенды для замеров, а в папке tools/perflab/TwidgetLab/widgets/attack — 28 виджетов, которые пытаются действовать без настоящего нажатия. Шестнадцать из них шлют боту данные: по таймеру раз в 50 мс, синтетическим кликом, отложенным вызовом после клика, через подпись к полю формы (label), перетаскиванием. Двенадцать пробуют без нажатия открыть ссылку или уйти со страницы — ссылкой поверх кнопки, отправкой формы, автообновлением страницы. Папка появилась в репозитории с коммитом 23 октября, за три дня до сборки с request().

В этих стендах отправка ещё называется TelegramWidget.sendMessage. В сборке 283391 такого вызова нет — есть request(), а вызов вне нажатия скрипт получает как ошибку с причиной no-activation.

Что ещё было в убранных примерах

В папке widget-sandbox сборки 283381 лежали два набора по 20 виджетов — with-profile и without-profile. Имена файлов — это просьбы: «show me a mortgage calculator», «make a bar chart of the top 6 programming…», «create a pomodoro timer», «create a flashcard widget for 8 Spanish…». Объект TelegramWidget упоминают 12 файлов первого набора и ни одного из второго. Похоже на виджеты, собранные моделью по одним и тем же просьбам с описанием возможностей песочницы и без него, — это вывод по содержимому, сама сборка этого не говорит. Слово profile есть и в объекте TelegramWidget: свойство profile равно 1, а isProfileAtLeast() проверяет уровень.

Остальное в папке — проверки совместимости (Chart.js, D3, three.js, WebGL, формы, перетаскивание, localStorage), карточки для витрины по темам «покупки», «работа», «жизнь», «путешествия» и стресс-тесты.

Где ещё виджеты

Виджеты в сообщениях как формат следующего обновления 13.2 описывали 25 октября. Как виджет обращается к боту, что он видит о зрителе и где проходят границы запросов, в этом описании не было.