IT-инфраструктура для бизнеса и творчества
Разработка
Yandex.Cloud

Делаем чат-бот в Telegram на функциях: пошаговая инструкция

Рассказываем, как с помощью бессерверных вычислений сделать своего чат-бота в Telegram с нуля. Он сможет отвечать на запросы простыми сообщениями и отправлять картинки. Подойдет для организации процессов разработки, поддержки и продаж.

Мы напишем простого бота-заготовку, которого можно использовать для написания полноценного приложения, добавив нужную вам функциональность.

Такого бота можно написать с использованием serverless-подхода в крупных облаках, например, с помощью AWS Lambda или Google Cloud Functions. Мы напишем функцию на Yandex Cloud Function, сделаем Object Storage, чтобы хранить файлы, и спрячем нашу serverless-архитектуру от пользователя с помощью сервиса Yandex API Gateway, который позволяет выступить фронтом для пользователя.

Шаг 1. Создайте бота с помощью BotFather

Прежде всего нужно зарегистрировать наше приложение в Telegram. Найдите в мессенджере главного бота BotFather и наберите команду /newbot. Для этого достаточно написать его имя user name и account name. В нашем случае account name — Serverless Hello Telegram Bot, username — ServerlessHelloTelegramBot. В результате вы получите token, запомните его, он потребуется на следующих этапах.

С помощью команды /setuserpic установите иконку для вашего бота. В нашем случае это картинка с именем sayhello.png.

На этом этапе у нас уже есть заготовка для бота. Дальше переходим в Yandex.Cloud и начинаем создавать инфраструктуру для бота.

Шаг 2. Создайте сервисный аккаунт

Войдите в ваш рабочий каталог.

Создайте новый сервисный аккаунт для работы telegram-бота. Например, serverless-telegram. Задайте роли для него: serverless.functions.invoker и editor. Роль serverless.functions.invoker необходима для запуска функций.

Обратите внимание: editor перекрывает роль serverless.function.invoker.

Запомните идентификатор созданного сервисного аккаунта.

Шаг 3. Создайте Object Storage

Нам нужно где-то хранить файлы для бота. Перейдите в каталог и выберете сервис Object Storage. Нажмите кнопку Создать бакет.

Введите имя бакета: for-serverless-hello-telegram-bot. Затем задайте максимальный размер в 1 ГБ и установите параметр Доступ на чтение объектов — Публичный.

Когда мы размещаем объект, есть возможность поместить его в два разных хранилища: стандартное с быстрым доступом или холодное. Холодное — это долговременное хранилище, оно отдельно тарифицируется и чаще всего используется для бэкапов. В нашем случае выбираем стандартное.

Загрузите картинку в созданный бакет. В нашем случае это тот же самый файл, который мы установили аватаром для бота: sayhello.png. Получите ссылку на загруженную картинку и проверьте её доступность по ссылке в браузере.

Шаг 4. Опубликуйте картинку через API Gateway

Давайте опубликуем нашу картинку через сервис API Gateway. Таким образом мы создадим заготовку, чтобы пользователь мог работать с нашим telegram-ботом, но не видел, что находится внутри. Перейдите в каталог и выберете сервис API Gateway. Нажмите кнопку Создать API-шлюз:

Введите имя for-serverless-hello-telegram-bot и вставьте спецификацию:

openapi: 3.0.0 info: title: for-serverless-hello-telegram-bot version: 1.0.0 paths: /sayhello.png: get: x-yc-apigateway-integration: type: object-storage bucket: for-serverless-hello-telegram-bot object: sayhello.png presigned_redirect: false service_account: IDYOURACCOUNT operationId: static

Обязательно замените:

· sayhello.png — на имя вашей картинки (два раза).

· for-serverless-hello-telegram-bot — на имя вашего бакета.

· IDYOURACCOUNT— на id вашего сервисного аккаунта, созданного ранее.

После опубликования API-шлюза в спецификации появится секция servers с адресом url. Через этот адресом вы сможете обратиться к ранее опубликованной картинке. В нашем случае к sayhello.png.

Проверяем, все работает. Отлично! Мы соединили два сервиса. У нас, с одной стороны, есть Object Storage, в котором что-то хранится. И есть API Gateway, который публично предоставляет доступ к каким-то ресурсам.

Шаг 5. Создадим Cloud Function

Идем дальше. Теперь нам нужно сделать функцию. Перейдите в каталог и выберете сервис Cloud Functions. Нажмите кнопку Создать функцию. Задайте имя. В нашем случае: fshtb-function.

Мы создали объект, но сама функция еще не создана. Нам необходимо выбрать среду выполнения. Внутри Yandex.Cloud несколько разных сред. Мы выберем nodejs, у нас есть там python.

Выберете среду выполнения node12js-preview.

Создайте два файла: index.js и package.json. Здесь мы используем известный в JavaScript-комьюнити фреймфорк Telegraf.

Для файла index.js скопируйте следующий код:

const { Telegraf } = require('telegraf'); const bot = new Telegraf(process.env.BOT_TOKEN); bot.start((ctx) => ctx.reply(`Hello. \nMy name Serverless Hello Teleram Bot \nI'm working on Cloud Function in the Yandex.Cloud.`)) bot.help((ctx) => ctx.reply(`Hello, ${ctx.message.from.username}.\nI can say Hello and nothing more`)) bot.on('text', (ctx) => { ctx.reply(`Hello, ${ctx.message.from.username}`); }); module.exports.handler = async function (event, context) { const message = JSON.parse(event.body); await bot.handleUpdate(message); return { statusCode: 200, body: '', }; };

Этим мы научили нашего бота отвечать на команды /start, /help, а также на любой написанный текст. Но давайте сразу изменим функцию так, чтобы она на любой текст отправляла картинку. Для этого внесите изменение в файл index.js.

bot.on('text', (ctx) => { ctx.replyWithPhoto('YOURAPIGWURL/sayhello.png'); ctx.reply(`Hello, ${ctx.message.from.username}`);

Важно: не забудьте заменить YOURAPIGWURL на url из секции servers вашего API-шлюза. После создания версии функции, ваш telegram-бот будет отправлять вам картинку из Object Storage, опубликованную через API-шлюз.

Для файла package.json введите следующий код:

{ "name": "ycf-telegram-example", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "", "license": "MIT", "dependencies": { "telegraf": "^3.38.0" } }

Укажите точку входа index.handler. Увеличьте таймаут до 5 секунд. В переменные окружения добавьте переменную BOT_TOKEN со значением токена вашего telegram-бота, полученного ранее.

Обязательно нажмите кнопку — Создать версию. После создания функции сделайте ее публичной. Запомните идентификатор вашей функции.

Шаг 6. Свяжем функцию и бота в Telegram

Теперь нам нужно соединить нашу функцию с ботом в Telegram. Для этого вернитесь в сервис API Gateway и выберете ранее созданный API-шлюз с именем for-serverless-hello-telegram-bot. Измените его спецификацию — в конце добавьте в нее секцию fshtb-function:

/fshtb-function: post: x-yc-apigateway-integration: type: cloud-functions function_id: IDYOURFUNCTION operationId: fshtb-function

ЗаменитеIDYOURFUNCTION на id вашей функции, созданной ранее. Сохраните изменения.

Теперь для создания связи между ранее созданным телеграм-ботом и вашей функцией нужно запустить команду в терминале (командной строке).

curl --request POST --url https://api.telegram.org/botYOURTOKEN/setWebhook --header 'content-type: application/json' --data '{"url": "YOURAPIGWURL/fshtb-function"}'

Замените YOURTOKEN на токен вашего telegram-бота и YOURAPIGWURL на url из секции servers вашего API-шлюза. Нажмите Enter. После положительного ответа связь создана, и вы сможете поговорить со своим ботом. Если не вылезать из лимитов Free tier, ваш бот обойдется вам в 0 рублей.

Оставляем полезные ссылки на каналы и сообщества Serverless в мире и в России, где вы сможете найти еще больше полезных примеров использования функций:

Подписывайтесь на блог Yandex.Cloud, чтобы узнавать еще больше новостей и историй об IT и бизнесе.

Другие истории, которые активно читают наши подписчики:

(function () { let cdnUrl = `https://specialsf378ef5-a.akamaihd.net/SelectelBranding/images/` let previousArticleNumber = null let currentArticleNumber = 0 let platform = 'Desktop' let articles = [ { name: 'camera', url: `${cdnUrl}CameraCat`, text: 'умную камеру для\u00A0наблюдения за\u00A0котиками', link: 'https://vc.ru/selectel/306690', num: 3 }, { name: 'chill', url: `${cdnUrl}ChillCat`, text: 'трекер, который подскажет, когда пора отдохнуть', link: 'https://vc.ru/promo/288561-eye-tracker', num: 1 }, { name: 'cloud', url: `${cdnUrl}CloudCat`, text: 'котика: даёшь ему «пять», а\u00A0он делает бэкап в облако', link: 'https://vc.ru/dev/294799-maneki-neko', num: 2 } ] let buttonCycle = document.querySelector('.button--cycle') let buttonChoose = document.querySelector('.button--choose') let buttonMobile = document.querySelector('.button--mobile') let textField = document.querySelector('.selectel-footer-subtitle') let imageAgent = document.querySelector('.image--agent') let banner = document.querySelector('.selectel-footer') buttonCycle.addEventListener('click', cycleClick) buttonChoose.addEventListener('click', () => sendEvent(`Promo ${articles[currentArticleNumber].num} Left`, 'Click')) buttonMobile.addEventListener('click', () => sendEvent(`Promo ${articles[currentArticleNumber].num} Left`, 'Click')) let media = window.matchMedia("(max-width: 570px)") media.addEventListener('change', matchMedia) function matchMedia() { if (media.matches) { platform = 'Mobile' } else { platform = 'Desktop' } update() } matchMedia() function cycleClick(event) { sendEvent(`Promo ${articles[currentArticleNumber].num} Right`, 'Click') if (event) { event.preventDefault() event.stopPropagation() } window.open('https://vc.ru/tag/selectelDIY', '_blank') //cycle(event) } function cycle(event) { // incrementArticleNumber() textField.innerHTML = generatedText() imageAgent.src = articles[currentArticleNumber].url + platform + '.svg?3' imageAgent.setAttribute("class", "") imageAgent.classList.add('image--agent', articles[currentArticleNumber].name) banner.href = articles[currentArticleNumber].link } function update() { banner.href = articles[currentArticleNumber].link imageAgent.src = articles[currentArticleNumber].url + platform + '.svg' textField.innerHTML = generatedText() } function incrementArticleNumber() { previousArticleNumber = currentArticleNumber if (currentArticleNumber >= articles.length - 1) { currentArticleNumber = 0 } else { currentArticleNumber++ } } const sendEvent = (label, action = 'Click') => { const value = `SelectelDIY — loc: Footer — ${label} — ${action}`; if (window.dataLayer !== undefined) { window.dataLayer.push({ event: 'data_event', data_description: value, }); } }; function generatedText() { let defaultText if (platform === 'Desktop') { defaultText = `Мы тут собрали %text%. Хотите научим?` } else { defaultText = `Мы тут собрали %text%.` } return defaultText.replace('%text%', articles[currentArticleNumber].text) } function getRandom(min, max) { min = Math.ceil(min) max = Math.floor(max) return Math.floor(Math.random() * (max - min + 1)) + min } (function create() { currentArticleNumber = getRandom(0, articles.length - 1) cycle() let page = document.querySelector('.page--entry') if (page) { function insertAfter() { let parents = page.querySelectorAll('[data-id="7"]') let referenceNode = parents[0] referenceNode.parentNode.insertBefore(banner, referenceNode.nextSibling); loaded() } setTimeout(() => insertAfter(), 0) } }()) function loaded() { banner.classList.add('loaded') } loadImages([ `${cdnUrl}CameraCatDesktop.svg`, `${cdnUrl}ChillCatDesktop.svg`, `${cdnUrl}CloudCatDesktop.svg`, `${cdnUrl}CameraCatMobile.svg`, `${cdnUrl}ChillCatMobile.svg`, `${cdnUrl}CloudCatMobile.svg?3`, ]) function loadImages(urls) { return Promise.all(urls.map(function (url) { return new Promise(function (resolve) { var img = document.createElement('img'); img.onload = resolve; img.onerror = resolve; img.src = url; }); })); } }())
0
22 комментария
Популярные
По порядку
Написать комментарий...

  мы используем известный в java-комьюнити фреймфорк Telegraf

Кажется, вы хотели написать «JavaScript-комьюнити»

10

Спасибо! Поправили и объяснили нашему копирайтеру:) 

6

Если статьи пишет пишет копирайтер, который не может отличить JavaScript от Java, ценность такой статьи — 0 рублей.

–2

Ну слушайте, очевидно же, что в одиночку такой копирайтер просто не смог бы написать этот текст. Я не имею отношения к Яндексу, но по своей работе знаю, что такие тексты часто делаются «вмногером»: от кого-то техническая экспертиза, от кого-то грамотный русский, от кого-то оформление. И иногда на стыках вылезают шероховатости, но это «вкрались ошибки в материал от знающего человека», а не «статья с ценностью 0 рублей».

Похоже, тут в основу материала лёг доклад Антона Черноусова, где Антон случайно оговорился (позже поправился), а в текст пролезла его оговорка. ред.

4

А ещё копирайтер неграмотен и не может понять, что слово "выберите" он всё это время писал неправильно. Яндекс, перестаньте нанимать школоту

–2

Слишком много движений лишних.. а под капотом все равно интерпретаторы питона и ещё там чего то. Не проще ли получить API Key и самому поднять сервер бота если надо, дома либо арендовать вм у провайдера? И бота умного сделать можно, а не вот эти ваши картинки или единообразные ответы) Учите ЯП, иначе всегда будете в таких ситуациях зависимы от тех, кто их когда то освоил + платить им за пустяковых ботов.

4

Всё правильно!
А то ишь, понапишут микросервисов бессерверных своих, и давай по подъездам докером через кубернетис долбиться

6
Уполномоченный файл

Спасибо за инструкцию. Статью в закладки. Попробую повторить на досуге. Затем отпишусь... 👨🏻‍💻

4

Если не вылезать из лимитов Free tier, ваш бот обойдется вам в 0 рублей.

До 16 октября как я понимаю. После этой даты лимиты могут урезать, а потом ещё и ещё.

4

Кстати, да
@Yandex.Cloud что нас ждёт после 16го?
В плане free tier

0

Если есть какие-то вопросы по инструкции, можно обратиться в наше serverless-сообщество, помогут) https://t.me/YandexCloudFunctions

3

А, в том смысле, что это бот что-то отвечает и всё. Ну, да. Это просто шаблон. Дальше учим js и добиваем боту возможностей.

1

Ну нееее, что-то там учить! Не! 😆😆😆😆😆

0

Если писать статью про все сценарии ботов, то тут такой мануал на пару томов будет...)

3

Удаление гланд через задний проход

0

Это просто ответчик?
А чтобы он чтото делал, как тогда?

0

Что, простите?

0

А это не старая статья а то pyton37preview(!) Nodejs12preview или эт Я - тормозит?

0

Абсолютно бесплатный сервер для бота поднят на Heroku под Node.js
Redis в комплекте для "всякого такого"

0

на 4ом шаге в API-шлюзе ошибку выдает при создании:Invalid openapi spec: while scanning for the next token found character '\t(TAB)' that cannot start any token. (Do not use \t(TAB) for indentation) in 'reader', line 2, column 1: info: ^ at [Source: (StringReader); line: 1, column: 15], все заменяю, на свои названия и прочее и никак. в чем проблема?

0

скопируй в редактор типа vsc или pycharm, выполни "Convert indentation to Spaces", скопируй обратно. Не думаю, что актуально, но вдруг кому поможет)

0
Читать все 22 комментария
«Яндекс» попросил ЦБ «принять меры» из-за фальшивого сайта «Яндекс-банка» Статьи редакции

Домен зарегистрировало частное лицо на следующий день после объявления о переименовании «Акрополя».

Эксперимент: оформляем банковские карты без бумажных документов

Оформление затянулось, но проект все равно продолжили развивать.

Это Элви Рэй Смит: он мечтал создать анимационный фильм на компьютере, соосновал Pixar, но ушёл из компании из-за Джобса Статьи редакции

Смит создал альфа-канал, который используется в Photoshop и других редакторах, работал в Xerox и LucasFilm и убеждал всех, что за компьютерной графикой будущее, однако всерьез его никто не воспринимал – кроме Стива Джобса.

Мвидео, облажались. Дважды

#жалобамвидео или как продать витринный образец

На сайтах с эквайрингом от ПСБ появился Yandex Pay

Покупатели с аккаунтом «Яндекса» смогут оплачивать покупки, не вводя данные карты.

Минэкономразвития РФ приняло предложения РСПП по совершенствованию рынка интеллектуальной собственности

На актуализации плана дорожной карты ТДК (трансформация делового климата) «Интеллектуальная собственность» в соответствии с пожеланиями бизнес-сообщества настоял первый заместитель председателя правительства Андрей Белоусов.

Илон Маск отправил сотрудникам письмо о риске банкротства SpaceX из-за медленного производства двигателей — CNBC Статьи редакции

Компании нужно ускориться, чтобы запускать ракеты минимум раз в две недели в 2022 году, написал Маск.

Лайфхак в подборе: как ATS-системы помогают экономить время и бюджет рекрутеров

Процесс рекрутинга существенно не отличается от компании к компании. Есть несколько типичных шагов, которые проходит рекрутер: размещение вакансии на джоб-бордах, поиск соискателей по базам, согласование кандидатов с заказчиками, собеседование, выдача и проверка тестового задания, проверка кандидата службой безопасности.

Kornia - Python библиотека для обработки изображений в задачах CV

В этой статье я хотел бы познакомить читателей с библиотекой для ЯП python — Kornia, имеющей богатый функционал в области computer vision. Библиотека написана с использованием pytorch, в ее основе лежат готовые решения, такие как torchvision, PIL, skimage, tf.image, OpenCV. В Kornia реализована возможность выполнения вычислений не только с…

Чем отличается подход к бизнесу заказчиков с США и Европы от наших?

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

null