Руководство: как сделать простого бота для Телеграм на JS для новичка в программировании

С тех пор, как Telegram в 2015 году представил ботов, другие мессенджеры тоже добавили ботов на свои собственные платформы. Но Telegram по-прежнему опережает своих конкурентов благодаря обширному боту API и свободе, которую он предлагает разработчику. API-бот это интерфейс на основе HTTP, созданный для разработчиков, желающих создавать боты для Telegram.

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

Как работают боты

Чтобы получать обновления от Telegram, вам понадобится токен (token). Все обновления и взаимодействия с вашим ботом в Telegram сохраняются, так что вы можете обратиться к ним, послав запрос по этому URL с указанием токена:

 https://api.telegram.org/bot/METHOD_NAME

С чего начать

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

Замените вышеуказанный URL на ваш токен и укажите один из методов API-бота Telegram. Давайте используем метод getMe
.

https://api.telegram.org/bot/getMe    // --> {"ok":true,"result":{"id":437852999,"is_bot":true,"first_name":"Reddit Bot","username":"SimpleReddit_Bot"}}

Отлично. Но как это сделать в NodeJS? В общем-то так же. Каждый раз, когда нам нужно обновление, мы отсылаем запрос по адресу с указанием нужного метода.

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

Начинаем писать код

Инициализируйте процесс и установите Telegraf:

npm init
npm install telegraf --save

Теперь давайте добавим его к нашему скрипту и создадим простой бот:

const Telegraf = require('telegraf');  const app = new Telegraf(YOUR_TOKEN_HERE);    app.hears('hi', ctx => {   return ctx.reply('Hey!');  });    app.startPolling();

Что происходит? У Telegraf есть свои собственный методы чтобы сделать за нас большую часть работы. Мы может использовать этот метод чтобы ответить на сообщение пользователя:

Reddit bot

Давайте рассмотрим пример. Мы будем отсылать верхний пост из сабредита по запросу пользователя. Установим библиотеку axios чтобы упростить отсылку запросов GET и получение данных от Reddit.

npm install axios --save
const axios = require('axios'); // add axios    // handle the reaction everytime user sends a text message  app.on('text', ctx => {    // ctx object holds the Update object from Telegram API   // So you can use everything you see there    // get the text message sent by user   const subreddit = ctx.message.text;    // GET the data from Reddit API   axios.get(`https://reddit.com/r/${subreddit}/top.json?limit=10`)   .then(res => {    // data recieved from Reddit   const data = res.data.data;    // if subbreddit does not exist   if (data.children.length  console.log(err));  });

Когда пользователь отсылает название сабредита, мы будем брать верхний пост из сабредита и отсылать ссылку на него. Просто, а?

Сохранение состояния (state)

Представьте, что пользователь хочет иметь возможность выбрать не только верхний пост, но и самый популярный или самый новый. Нам нужно сохранить последние команды, которые он использовал, чтобы суметь ответить правильно. Заметьте, что мы используем для этого метод command
вместо on
.

Вы можете создать команды на боте Telegram. Команды начинаются с «/» и являются кликабельными. Чтобы добавить команды к вашему боту, отправьте сообщение BotFather.

let
 state =
 {
}
;
    app.
command
(
'top'
,
 ctx =>
 {
    const
 userId =
 ctx.
message.
from
.
id;
      // if user id does not exist create one  
    if
 (
!
state[
userId]
)
      state[
userId]
 =
 {
 id:
 userId }
;
      // save/update user last command    
    state[
userId]
.
command =
 'top'
;
    return
 ctx.
replyWithMarkdown
(
`Enter a subreddit name to get *top* posts.`

)
;
  }
)
;
    app.
command
(
'hot'
,
 ctx =>
 {
    const
 userId =
 ctx.
message.
from
.
id;
    if
 (
!
state[
userId]
)
      state[
userId]
 =
 {
 id:
 userId }
;
    state[
userId]
.
command =
 'hot'
;
    return
 ctx.
replyWithMarkdown
(
'Enter a subreddit name to get *hot* posts.'
)
;
  }
)
;

Теперь мы можем отсылать правильные посты на основе фильтра. В нашем text-ответе:

const
 userId =
 ctx.
message.
from
.
id;
  // check if state and command exists and set defaults
  const
 type =
 !
state[
userId]
 ?
       'top'
 :
       state[
userId]
.
command ?
         state[
userId]
.
command :
         'top'
;
  axios.
get
(
`https://reddit.com/r/
${
subreddit}

/
${
type}

.json?limit=10`

)
    .
then
(
res =>
 [
      // do stuff
    ]
)

Встроенные кнопки

У ботов в Telegramесть интерактивные кнопки под названием InlineKeyboardMarkup
. Мы добавим кнопку next
, так что пользователь сможет получить следующий пост из данной категории.

Нам нужно извлечь из Telegraf особые методы для кнопок чтобы получить возможность работать с ними:

const
 {
 Markup }
 =
 require
(
'telegraf'
)
;

Во-первых, давайте добавим номер текущего поста к state. Каждый раз, когда пользователь запрашивает сабредит, нам нужно установить индекс в 0. В нашем методе text
:

 if
 (
!
state[
userId]
)
      state[
userId]
 =
 {
}
;
    state[
userId]
.
index =
 0
;

Вместо отсылки простого текста, мы отсылаем его со встроенной кнопкой в ответе axios
:

// old response, only text
  return
 ctx.
reply
(
link)
;
    // new response, with inline buttons
  return
 ctx.
reply
(
link,
   Markup.
inlineKeyboard
(
[
  // first argument is button's text
  // second argument is callback text
  Markup.
callbackButton
(
'➡️ Next'
,
 subreddit)
,
  ]
)
.
extra
(
)
  )
;

Мы можем управлять откликом с помощью метода on
, но в этот раз используем метод обновления callback_query
:

app.
on
(
'callback_query'
,
 ctx =>
 {
  // get info from callback_query object
    const
 subreddit =
 ctx.
update.
callback_query.
data;
    const
 userId =
 ctx.
update.
callback_query.
from
.
id;
    // check if user state and its properties exist
    let
 type;
    let
 index;
    try
 {
      type =
 state[
userId]
.
command ?
 state[
userId]
.
command :
 'top'
;
      index =
 state[
userId]
.
index;
    }
 catch
 (
err
)
 {
      return
 ctx.
reply
(
'Send a subreddit name.'
)
;
    }
    // reply with a popup to callback
    ctx.
answerCallbackQuery
(
'Wait...'
)
;
      axios.
get
(
`https://reddit.com/r/
${
subreddit}

/
${
type}

.json?limit=10`

)
      .
then
(
res =>
 {
        const
 data =
 res.
data.
data;
    // check if next one exists
        if
 (
!
data.
children[
index +
 1
]
)
          return
 ctx.
reply
(
'No more posts!'
)
;
          // send next link and update the user state with new index
        const
 link =
 `https://reddit.com/
${
data.
children[
index +
 1
]
.
data.
permalink}

`

;
        state[
userId]
.
index =
 state[
userId]
.
index +
 1
;
        return
 ctx.
reply
(
link,
           Markup.
inlineKeyboard
(
[
            Markup.
callbackButton
(
'➡️ Next'
,
 subreddit)
,
          ]
)
.
extra
(
)
        )
;
      }
)
      .
catch
(
err =>
 console.
log
(
err)
)
;
  }
)
;

Заключение

Как вы видите, мы создали простой Telegram-бот за считанные минуты. Создавать такие боты легко, но это не главное. Есть много вещей, которые вы можете делать с их помощью, например, отсылать фотографии, видео, документы и т. д.

Только представьте, все, что вы можете делать с огромным API, который постоянно улучшается с каждым обновлением.

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

Node.js module to interact with official Telegram Bot API. A bot token is required
and can be obtained by talking to @botfather.

npm install --save node-telegram-bot-api
const TelegramBot = ;
const token = ' YOUR_TELEGRAM_BOT_TOKEN ' ;
const bot = token polling : true ;
bot
;
bot
;

Note
: Development is done against the master
branch. Code for the latest release resides on the release
branch Experimental features reside on the experimental
branch.

We thank all the developers in the Open-Source community who continuously take their time and effort in advancing this project. See our list of contributors.

We have a Telegram channel where we post updates on the Project. Head over and subscribe!

Some things built using this library that might interest you:

  • tgfancy: A fancy, higher-level wrapper for Telegram Bot API
  • node-telegram-bot-api-middleware: Middleware for node-telegram-bot-api
  • teleirc: A simple Telegram ↔ IRC gateway
  • bot-brother: Node.js library to help you easily create telegram bots
  • redbot: A Node-RED plugin to create telegram bots visually

The MIT License (MIT)

Copyright © 2017 Yago

Предисловие

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

Зачем?

Итак, как я написал выше, я достаточно часто забываю посмотреть те фильмы, трейлер которых мне понравился. Я долго думал над решением этой проблемы. Первое, что пришло мне в голову — создание нативного приложения для смартфонов, но т.к. я никогда не занимался этим раньше, это заняло бы достаточно много времени и не факт, что конечный результат удовлетворял бы мои потребности, а также потребности потенциальных пользователей. Следующей идеей было создание PWA. Вполне себе интересный вариант для того, чтобы познакомиться с технологией, но ее я решил оставить на будущее. В то же самое время я достаточно давно пользуюсь Telegram и за все это время у меня скопилось достаточное количество ботов, которые периодически делают мою жизнь немного проще. В конечном счете взвесив все плюсы и минусы, я решил, что данная платформа отлично подойдет для данной идеи.

Выбор технологий

Если прошерстить Github на предмет наличия Telegram-ботов, то можно увидеть, что большая часть из них написана на Python. Python действительно замечательный язык и отлично подходит для данной идеи, но мне хотелось реализовать данный проект именно на NodeJS + TypeScript. Применив соответствующие фильтры я наткнулся на 2 достаточно популярных инструмента: node-telegram-bot-api и Telegraf.js. До этого опыта разработки ботов у меня не было, поэтому при выборе из этих двух было не совсем понятно на какие параметры следует смотреть. В итоге посмотрев документацию и полистав issues к каждой из библиотек, я остановился на Telegraf.js. Решающим фактором стало наличие механизма middlewares, который устроен таким же образом, как и в популярном фреймворке Express.js. Также Telegraf.js имеет более частую историю обновлений и хорошо структурированную документацию, из чего можно предположить, что разработчики прикладывают достаточно усилий для улучшения инструмента. Более того, он содержит тайпинги для TypeScript, что определенно является плюсом для выбранного стека.

Telegraf.js

Telegraf.js — основная библиотека, на которой был написан проект. Она использует несколько интересных подходов, которые позволяют создавать сложных ботов с сохранением простоты кода. Ниже представлены наиболее интересные механизмы:

  • Middlewares
    — да, это те же самые middlewares, которые доступны в Express.js. Когда пользователь что-то отправляет боту, сообщение вначале проходит через все зарегистрированные middlewares, а уже после попадает в контроллер. Middlewares могут модифицировать контекст, а также приостанавливать выполнение запроса, например, если обычный пользователь попытается попасть в область для администраторов. Telegraf.js предоставляет несколько крайне полезных middlewares «из коробки», но также можно использовать и разработанные лично. Именно middlewares считаются киллер-фичей Telegraf.js
  • Sessions
    — сессии, которые могут хранить в себе информацию, не привязанную к контроллеру. Можно привести аналогию с глобальными переменными, которые доступны из любого места в боте. Очень удобная вещь, которая используется, например, для локализации. Есть возможность хранить сессии в разных режимах — БД, redis, локальные файлы и т.д.
  • Webooks
    — бот может работать в двух режимах — long polling или Webhooks. И хотя работают они одинаково быстро, все же в production среде лучше использовать второй вариант. Таким образом, боту не нужно будет стучаться на сервер Telegram, вместо этого он сам будет получать все обновления.Важно:
    обязательным условием для Webhook’ов является поддержка сервером SSL/TLS. Также убедитесь, что порт, на котором запущен Webhook, открыт и доступен извне.
  • Markup
    — с помощью этого класса можно научить бота отвечать в markup/markdown разметке. Это важно если вы, например, делаете HTML-игру. Тем не менее в своем проекте я использовал его только для bold/italic текста.
  • Stage
    — крайне интересный модуль, который помог мне сохранить нервы и некоторое количество часов разработки. Здесь я расскажу подробнее. Бот умеет слушать сообщения от пользователя и, в зависимости от типа или содержимого сообщения, перенаправлять его в тот или иной контроллер, который выполнит свою функцию и отправит ответ пользователю. Выглядит это следующим образом:
    bot.hears('hello', async ctx => {   await ctx.reply('Hello!'); }); 
    

    Работает это просто: если написать боту hello, то он просто ответит Hello! Зная этот аспект работы, попробуем представить, как может работать бот для поиска фильмов:

    1. Пользователь пишет, что хочет перейти к поиску фильмов;
    2. Пользователь вбивает название фильма;
    3. Бот возвращает список фильмов и предлагает пользователю сохранить выбранный фильм в свою библиотеку;

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

    И тут в игру вступают сцены (Stages)! Легче всего будет рассказать на примере выше. Когда пользователь говорит, что хочет перейти к поиску фильмов, мы запускаем сцену. Внутри этой сцены стоят свои отдельные обработчики текста — bot.hears(…). Таким образом, все последующие сообщения будут относиться непосредственно к поиску фильмов до тех пор, пока пользователь не напишет сообщение для выхода и не покинет сцену. После этого обработчики внутри этой сцены деактивируются до тех пор, пока пользователь не вернется туда снова. Далее я расскажу, как я использовал сцены, чтобы сделать обработку событий максимально простой.

Структура проекта

Есть подозрение, что статья получится достаточно объемной, а потому, чтобы не растягивать ее еще больше, я опущу некоторые моменты. Например, чтобы бот начал работать, необходимо начать с получения Telegram Bot API token. Прочитать о том, как это сделать, можно в официальной документации Telegram или же в документации к Telegraf.js. Файловая структура проекта выглядит следующим образом:

Структура проекта

Работает это следующим образом:

  • src/controllers
    — это и есть те самые сцены, про которые было написано выше. В данном проекте каждая сцена представляет из себя отдельный контроллер, который занимается обработкой соответствующих запросов.
  • src/locales
    — переводы для разных языков. Telegraf.js позволяет относительно просто добавить локализацию к боту с использованием middlewares.
  • src/models
    — модели для MongoDB.
  • src/types
    — тайпинги для TypeScript. К сожалению, не все используемые в проекте библиотеки имеют тайпинги по умолчанию.
  • src/util
    — различные функции-помощники, которые используются в разных частях проекта. Здесь можно посмотреть как работает проверка доступности фильма для скачивания, управление сессиями, создание клавиатур, обработчики ошибок и еще много всего интересного.
  • src/bot.ts
    — главный файл, где производится вся подготовка и запуск бота.
  • src/telegram.ts
    — в данном случае мы создаем объект класса Telegram из библиотеки Telegraf.js. С помощью этого объекта мы можем отправлять пользователям сообщения первыми, используя их ID, а не ждать, пока они что-то напишут. Например, в данном проекте мы отправляем пользователю сообщение о том, что фильм, за которым он следил, уже можно скачать. Конечно же, данный объект предоставляет гораздо больше возможностей.
  • Все остальное
    — конфигурации для разных частей проекта, которые не будут рассмотрены в этой статье

Инициализация и запуск

Когда мы разобрались со структурой проекта, давайте посмотрим, как собственно происходит запуск бота. Опять же, в целях сокращения статьи я не буду вставлять сюда код целиком, а расскажу лишь про главные, на мой взгляд, моменты. Полный код вы можете посмотреть в репозитории, ссылка на который доступна в конце статьи. Начинается все с подключения к БД, где хранится информация о пользователях и отслеживаемых ими фильмах. После успешного подключения мы регистрируем все используемые в проекте сцены, задаем параметры для локализации и добавляем несколько middlewares — обработку сессий, локализацию, настройку сцен, а также несколько собственных. Одна из них, например, получает всю информацию о пользователе в соответствии с его ID и добавляет ее к контексту, который используется в контроллерах. Наконец, после всех основных приготовлений, мы запускаем бота либо в development (long polling), либо в production (Webhooks) режиме.Важно:
если вы используете разные методы получения обновлений (long polling и Webhooks), то при запуске бота в режиме long polling сперва удалите слушающий Webhook с помощью GET запроса на api.telegram.org/botYOUR_TOKEN/deleteWebhook. В противном случае бот может работать неправильно.

Обрабатываем пользовательский ввод

Ура! Бот работает, подключен к базе данных и готов принимать сообщения от пользователей. Но как правильно это сделать? В первую очередь, пользователям будет удобно пользоваться встроенной Telegram-клавиатурой. По сути, при нажатии на кнопки этой клавиатуры отправляются сообщения с содержимым этих кнопок. Дальше мы просто добавляем обработчики этого текста и выполняем определенные действия. В файле bot.ts как раз стоят такие обработчики. Так как бот поддерживает два языка, то и кнопки могут содержать разный текст — на русском и английском языках. В telegraf-i18n есть функция match, которая умеет обрабатывать нажатие одной и той же кнопки с разными языками. Большинство обработчиков в bot.ts выполняют единственную функцию — они запускают пользователя в соответствующую сцену. Так, у нас есть несколько разделов — поиск фильмов, моя коллекция, настройки и контакты. Для каждого из разделов есть своя сцена и своя кнопка, при нажатии на которую пользователь перемещается в соответствующую сцену.Важно:
обязательно добавьте обработчик, который будет выпускать пользователя из сцены, иначе они рискуют остаться там навсегда! Также полезным будет сделать одну общую команду (в боте используется /saveme), которая будет добавлена в каждую сцену и в главный файл. Эта команда будет служить выходом из любой сцены, а также сбрасывать пользовательские настройки. И вот, пользователь хочет перейти к поиску фильмов. Нажатием на соответствующую кнопку мы перемещаем его в сцену поиска. Для удобства, у каждой сцены есть своя папка с файлами, каждый из которых выполняет определенную функцию. Внутри сцены можно использовать свои middlewares, которые лежат в файле middlewares.ts. Например, используя middleware в сцене поиска, мы можем достаточно просто пробросить всю информацию о фильме в соответствующие методы, а не выполнять одну и ту же функцию каждый раз внутри них. В Telegram также есть inline-клавиатура. Возможно вы встречали сообщения с голосованиями, под которыми есть несколько полупрозрачных кнопок и при нажатии на одну из них количество голосов меняется. Эти кнопки и есть inline-клавиатура

Вот как это выглядит у Eve

В каждой кнопке содержится информация, при нажатии на кнопку она будет передана в соответствующий обработчик. Размер передаваемой информации не должен превышать 64 байта!
Чтобы научить бота слушать нажатия на кнопки, нам нужно зарегистрировать их с помощью bot.action(/trigger/, callback). В первый параметр попадают все данные, которые были привязаны к кнопке. Я решил использовать нечто вроде Actions из Redux, где к каждой кнопке привязан объект вида {a: actionName, p: payload}. При регистрации listeners мы можем использовать простой RegExp, например: bot.action(/actionName/, callBack). Все обработчики для inline-клавиатуры находятся в файлах actions.ts. Помимо этого, в некоторых сценах лежат файлы helpers.ts, которые содержат в себе небольшие функции вынесенные туда с целью разгрузить остальные файлы. По большей части там лежат генераторы клавиатур для разных действий со стороны пользователя.

Локализация

Так как это важная тема, я думаю ее стоит упомянуть отдельно. Как я говорил ранее, Telegraf.js содержит довольно большое количество middlewares, одной из которых является telegraf-i18n. В репозитории присутствует подробная инструкция и особых проблем у меня с этим не возникло, но все же я добавлю пару слов о том, как это работает в данном проекте. Есть папка locales, где лежат файлы для локализации, которые представляют из себя JSON объект вида { «ключ»: «перевод» }. Далее, везде, где нам нужно использовать разные языки, мы используем метод из этой библиотеки, куда передаем нужный нам перевод по ключу, а на выходе получаем соответствующий перевод. Для хранения информации о выбранном пользователем языке может использоваться сессия. Также еще раз стоит упомянуть про кнопки. В этой же библиотеке есть функция match, поэтому если текст на кнопке меняется в зависимости от языка, то эта функция поможет вам повесить правильный listener.Важно:
если вы собираетесь использовать локализацию и писать бота на TypeScript, не забудьте добавить папку с переводами в tsconfig.json, иначе код не скомпилируется. Например:

"include": ["src/locales/*.json"]

Заключение

Спасибо что дочитали до конца! В этой статье я постарался максимально подробно описать процесс создания Telegram-бота в целом, без сильной привязки к своему проекту. Я надеюсь, что после прочтения этой статьи, а также изучения исходного кода Eve, вы сможете с легкостью создать бота, который сможет помочь вам. Как и обещал, исходный код вы можете посмотреть на GitHub, а попробовать Eve в деле вот здесь. Я буду безмерно благодарен за любую критику и предложения по улучшению. Отдельно хочу отметить раздел в документации Telegraf.js с интересными open source проектами, на которые можно посмотреть и вдохновиться архитектурой и решениями. Я, в свою очередь, хочу отметить один из них — The Guard Bot. Поистине большой и хорошо сделанный бот, откуда я позаимствовал часть решений для своей разработки. А на данный момент я рассказал все, что хотел, буду рад ответить на ваши вопросы, предложения и комментарии!

Рейтинг автора
5
Подборку подготовил
Андрей Ульянов
Наш эксперт
Написано статей
168
Ссылка на основную публикацию
Похожие публикации