Относительно недавно команда Telegram выпустила обновление, с которым появилась возможность встраивать в мессенджер веб-приложения. Как заявляют разработчики: «Telegram-бот нового поколения станет полноценной заменой любому сайту».

Встроенные веб-приложения поддерживают авторизацию пользователя (проверку его подлинности), встроенную оплату через различных провайдеров, кастомизацию под выбранную пользователем тему и многое другое. И все это на JavaScript.

Эта статья — обзор основных моментов внедрения веб-приложениий в телеграм-ботов. Она написана скорее для новичков или тех, кто хочет быстро узнать, «что к чему».

Внедряем веб-приложения

Веб-приложения для встраивания Telegram поддерживает только защищенные протоколом HTTPS. Для тестирования вы можете обеспечить его самостоятельно с помощью самописного сертификата.

Переход к ним стал возможен через инлайн-кнопки (InlineKeyboardButton) и кнопки клавиатуры (KeyboardButton): к соответствующим типам был добавлен параметр web_app, необходимое значение для которого — добавленный тип WebAppInfo со ссылкой на веб-приложение. Пример реализации:

# Python 3: AIOgram v2.23.1
from aiogram import InlineKeyboardButton, KeyboardButton, WebAppInfo

# ...
ikb = InlineKeyboardButton("Перейти", web_app=WebAppInfo('https://<your_domain>'))
kb = KeyboardButton("Перейти", web_app=WebAppInfo('https://<your_domain>'))
# ...

Переход к одному веб-приложению возможен через меню: его можно закрепить с помощью BotFather. А также через закрепленные: достаточно создать кнопку с обычной ссылкой на бота (например, с помощью тех-же инлайн-кнопок) с параметром startattachInlineKeyboardButton("Перейти", url='https://t.me/<bot_name>?startattach'); однако переход через закрепленные оказывается доступным не для всех ботов.

Программируем веб-приложение

Чтобы подключать веб-приложение к пользователю Telegram, достаточно вставить в HTML-код страницы скрипт telegram-web-app.js в тег <head> до всех скриптов:

<head>
  <!-- ... -->
  <script src="https://telegram.org/js/telegram-web-app.js"></script>
  <!-- ... other scripts -->
</head>

После его подключения в скриптах, объявленных после, мы можем пользоваться всеми возможностями Telegram WebApps с помощью объекта window.Telegram.WebApp.

Список методов и объектов в window.Telegram.WebApp достаточо большой, полностью ознакомиться с каждым из них вы можете здесь. Например, вы можете поприветствовать пользователя, который перешел в веб-приложение через бота:

var WebApp = window.Telegram.WebApp;

WebApp.showAlert(`Добро пожаловать, @${WebApp.WebAppUser.username}.`);

Также мы можем обрабатывать различные события. Например, нажатие «главной» кнопки (MainButton) или кнопки «Назад» (BackButton), причем несколькими способами:

var MainButton = WebApp.MainButton;
var BackButton = WebApp.BackButton;

MainButton.show();
BackButton.show();

MainButton.onClick(function() {
  WebApp.showAlert("Хорошо, ты нажал на главную кнопку.");
});
WebApp.onEvent('mainButtonClicked', function() {
  /* also */
});

BackButton.onClick(function() {
  WebApp.showAlert("Нет пути назад!");
  
  BackButton.hide();
});
WebApp.onEvent('backButtonClicked', function() {
  /* also */
});

Особый интерес представляет возможность перейти к встроенной оплате из веб-приложения. Сделать это можно через метод openInvoice. Однако прежде необходимо сформировать ссылку на оплату (инвоис-линк). Формировать ее прямо в скрипте с помощью запроса к Telegram API оказывается плохой идеей, так-как для этого потребуется раскрыть секретные данные бота (токены бота, провайдера оплаты и т.п.). Поэтому формировать ее следует, например, на сервере веб-приложения (через бота), а из скрипта сделать запрос к нему:

let xhrURL = new URL('https://<your_domain>/<createInvoiceLink>');
xhrURL.searchParams.set('title', ...);
/* ... setting other non-private optional parameters */

let xhr = new XMLHttpRequest();
xhr.open('GET', xhrURL);
xhr.send();
xhr.onload = function() {
    WebApp.openInvoice(JSON.parse(xhr.response).result);
}

После чего у нас будет открываться карточка товаров/услуг с возможностью их оплатить. На примере встроенной в веб-приложение оплаты Liot:

Также мы можем обрабатывать события после закрытия окна оплаты:

WebApp.onEvent('invoiceClosed', function(object) {
  if (object.status == 'paid') {
    WebApp.close();
  } else if (object.status == 'failed') {
    WebApp.showAlert("Не беспокойтесь. Мы сохраним ваш выбор.");
  }
});

Особое внимание стоит уделить безопасности. Для проверки подлинности пользователя («Настоящий ли пользователь зашел в веб-приложение и через Telegram ли?») предусмотрен способ валидации получаемых [через скрипт] данных.

Содержимое переменной initDatawindow.Telegram.WebApp] представляет из себя строку формата ключ=значение, разделенных & — в общем и целом, параметры для поиска в ссылках. Формат включает в себя параметры auth_date, query_id, user и hash. Из этой строки состоит еще одна — data-check-string. Она включает в себя почти те же параметры, кроме hash, только отсортированные и разделенные \n. Именно она участвует в валидации.

Следующее значение, участвующее в валидации — secret_key. Оно представляет из себя хэш варианта HMAC-SHA-256 по значению токена бота и ключу "WebAppData".

И так, по условию, если хэш варианта HMAC-SHA-256 по значению data-check-string и ключу secret_key равно hash [из initData], значит пользователь подлинный. Какой псевдо-код демонстрируют разработчики:

data_check_string = ...
secret_key = HMAC_SHA256(<bot_token>, "WebAppData")
if (hex(HMAC_SHA256(data_check_string, secret_key)) == hash) {
  // data is from Telegram
}

Очевидно, что генерировать secret_key и валидировать следует на стороне, например, на сервере, чтобы не раскрыть токен бота. Пример реализации:

let initDataURLSP = new URLSearchParams(WebApp.initData);
var hash = initDataURLSP.get('hash');

initDataURLSP.delete('hash');
initDataURLSP.sort();
var checkDataString = initDataURLSP.toString().replaceAll('&', '\n');

let xhrURL = new URL('https://<your_domain>/<userIsValid>');
xhrURL.searchParams.set('hash', hash);
xhrURL.searchParams.set('checkDataString', checkDataString);

let xhr = new XMLHttpRequest();
xhr.open('GET', xhrURL);
xhr.send();
xhr.onload = function() {
    if (JSON.parse(xhr.response).result == true) {
      WebApp.showAlert(`Добро пожаловать, @${WebApp.WebAppUser.username}.`);
    } else {
      WebApp.showAlert("Ты что, хакер?");
      WebApp.close();
    }
}

Дизайн веб-приложения можно подстраивать под пользователя с помощью объекта ThemeParams, внутри которого содержатся подстроенные под выбранную пользователем тему мессенджера: фон, текст, ссылки и т.п. При этом, вы можете использовать их не только в скриптах, но и в стилях (CSS) с помощью var:

h1 {
  color: var(--tg-theme-text-color);
}

p {
  color: var(--tg-theme-hint-color);
}

/* ... */

Возможности встроенного в чат-бот веб-приложения вы можете также увидеть на примере Liot — концептуального телеграм-бота для записи в один из салонов красоты. Он же на ГитХабе по ссылке.

Надеюсь, вы смогли из этой статьи вынести для себя что-то полезное :)

Комментарии (5)


  1. night_admin
    20.12.2022 11:33
    +2

    По-моему, вы не раскрыли тему безопасности. В вашем "примере реализации" бэкэнд возвращает простое true/false, что легко можно заменить на другое значение в любом дебаг-прокси и получить доступ к вебаппу от имени юзера (MITM).

    Уверен, что кто-то будет использовать эту статью как туториал, поэтому предостерегаю от тупого копирования. О безопасности действительно надо заботиться, но не так, как предложил автор.


  1. a1exov
    20.12.2022 11:33
    -1

    Чувствую, что это тренд 2023.


  1. elchako
    20.12.2022 11:33

    Спасибо за статью! Хотелось посмотреть на реализацию бота Liot, но он не отвечает.


    1. fruitourist Автор
      20.12.2022 11:36

      Извиняюсь, запущен


  1. night_admin
    20.12.2022 11:33
    +2

    По-моему, вы не раскрыли тему безопасности. В вашем "примере реализации" бэкэнд возвращает простое true/false, что легко можно заменить на другое значение в любом дебаг-прокси и получить доступ к вебаппу от имени юзера (MITM).

    Уверен, что кто-то будет использовать эту статью как туториал, поэтому предостерегаю от тупого копирования. О безопасности действительно надо заботиться, но не так, как предложил автор.


  1. palyaros02
    21.12.2022 11:48

    "Относительно недавно" - да уже года 2 как, может и больше. На сайте телеги есть отличная подробнейшая документация, лучше бы ее перевёл. Статья не особо полезная, представления о теме не даёт. И читается тяжело, как будто машинный перевод