Безсерверный телеграм бот на облаке Яндекса

2cf0bde1734d702d88bd15ec3e6cb539

Топ 5 моментов, при разработке бота ТГ на R, которые сложно или невозможно понять из документации.

Возьмем пример https://cloud.yandex.ru/ru/docs/functions/tutorials/telegram-bot-serverless

Если вы когда нибудь читали документацию Яндекс облака, вы в курсе. Для остальных могу пояснить. Возьмите лапидарный текст, удалите из него ясность и чёткость и вы получите документацию Яндекс облака.

В статье я хочу поделиться теми моментами, которые всплыли при разработке бота в телеграм, не описаны в документации и понять их можно только путём длительной и очень неудобной отладки через переусложненные логи вызова.

Последовательность шагов понятна из документации. Делаем API, на него вебхуком телеграмм присылает обновления в формате json, API дёргает безсерверную функцию и отдаёт в неё json, парсим json как обычно, отправляем ответ ботом telegram.bot.

Удобство в том, что безсерверная функция это небольшой компьютер, который включается на момент обработки запроса и уничтожается после выполнения или по таймауту. Никаких докеров и к8, всё автоматически настроено. Но не всё так радужно, есть и подвохи.

Подвох 1. Полностью проигнорирована безопасность.

Предлагается создать публичную функцию и выдать функции публичный доступ. Крайне не рекомендую так делать. Создайте сервисный аккаунт, дайте ему роли в каталоге editor, serverless.functions.invoker, functions.functionInvoker. В код

/fshtb-function:
    post:
      x-yc-apigateway-integration:
        type: cloud_functions
        function_id: <идентификатор_функции>
      operationId: fshtb-function

добавьте id сервисного аккаунта

  /fshtb-function:
    post:
      x-yc-apigateway-integration:
        type: cloud_functions
        function_id: <идентификатор_функции>
        service_account_id: <Идентификатор_сервисного_аккаунта>
      operationId: fshtb-function

Это единственное изменение, которое потребуется. Делать функцию и/или бакет публичным крайне не рекомендую.

Подвох 2. Из примера можно подумать, что есть полный доступ к бакету.

На самом деле с бакета можно только читать, записать ничего не получится, подключить бакет к функции тоже невозможно, хотя об этом вы узнаете только после того, как полностью заполните всю форму на подключение бакета (фича в стадии preview, доступ не дают, кнопку «добавить» замазывают сереньким прям в момент заполнения последнего поля формы, нажать её нельзя).

Подвох 3. Как читать данные с секретами.

После активного курения мануалов вы поймете, что секреты добавляются переменными окружения и нигде не описан способ их прочитать. Для некоторых сред, таких как Питон и СиДиез, я нашел примеры в других разделах документации, для R никаких примеров нет. Поэтому делюсь тут. Чтобы прочитать переменную, указанную в настройках как bot_token нужно выполнить код

Sys.getenv("bot_token")

например, так

bot <- Bot(token = Sys.getenv("bot_token"))

Подвох 4. Вам понадобится документация по API telegram, но ссылки на него нигде нет.

Лично мне хватило того, что описано тут

https://core.telegram.org/bots/api#message

Где хорошо и понятно описана структура объекта, который возвращает вебхуком.

и тут

https://core.telegram.org/bots/api#getting-updates

А фраза в документации «Выполните запрос в терминале:» вообще ничего не объясняет.

На самом деле именно этот запрос и заставит API телеграма отсылать все обновления к шлюзу Яндекса, в шлюзе вы как раз и пропишите, какая безсерверная функция для обработки запроса запускается. И еще, после выполнения запроса вы полностью теряете возможность вручную запросить обновление у бота через API. Сохранил вам пол дня поисков и бесплодных попыток отладки через getUpdates, не благодарите.

Подвох 5. До ответа telegram API вы всё равно не доберетесь так, как описано в документациях.

Самый неочевидный момент, когда бот настроен и работает, как расширять его функциональность и добраться до вожделенного update$message.

Запросы к шлюзу API рандомным образом шифруются или не шифруются через base64. Узнать заранее, будет ли зашифровано body запроса шлюза, невозможно. Шлюз отдаёт 2 объекта event и context, они описаны в документации, не буду описывать их структуру. Информация, передаваемая вебхуком вся в элементе event$body (это совсем не очевидно сразу). Поэтому поступаем так:

#$body ответа api telegram завернуто в $body api шлюза. 
# вот так вот разворачиваем эту цепочку и получаем струтуру из документации к библиотеке telegram.bot.
library(telegram.bot)
library(httr) #модуль есть по умолчанию в функции, отдельно устанавливать не надо, тут как пример.
library(jsonlite)

handler <- function(event, context) {
       # bot_token прописать в переменные окружения при создании функции, вместе с другими секретами
       bot <- Bot(token = Sys.getenv("bot_token"))
     
       if (event$isBase64Encoded) {
          handler_update_body <- base64_dec(event$body)
       } else {
          handler_update_body <- event$body
       }
     
       handler_update_body_parsed <- fromJSON(handler_update_body) 
       # вот тут уже всё работает, как описано в документации. Например:
  
       text <- handler_update_body_parsed$message$text
       current_user <- handler_update_body_parsed$message$from$id
  
       bot$sendMessage(chat_id = update$message$chat$id,
                     text = sprintf("Hello %s!", update$message$from$username))
      
       if (startsWith(text, "/start")) {
                    
                    start(bot,handler_update_body_parsed)
                    
       }
  }

© Habrahabr.ru