Несколько месяцев назад как-то больше по приколу написал телеграм-бота с интеграцией GPT. Это было, кстати, ещё до того, как весь телеграм утонул в этих ботах. После этого решил, что можно попробовать эту область на фрилансе. За эти месяцы сделал миллион всяких телеграм-ботов с GPT, другими нейронками с доступным API (и даже недоступным в случае с Midjourney), всякие магазины и тому прочих ботов. Этот опыт позволил прошариться немного за телеграм-ботов и в этом материале расскажу об основных моментах, с которыми Вы скорее всего столкнётесь при написании телеграм-ботов на NodeJS. Если есть чем меня дополнить или, возможно, поправить, то буду рад обратной связи. Для начала создадим NodeJS проект и установим туда пакет для работы с телеграм-ботом через npm: И инициализируем библиотеку для работы с телеграм-ботом в проект: Далее нам нужно создать экземпляр класса TelegramBot. В конструктор нам необходимо передать токен нашего бота (создать бота и получить токен для него можно в BotFather): Я для хранения таких переменных, как токен бота, использую модуль dotenv, однако, это можно представить в следующем виде: Обратите внимание, что вместе с токеном бота я передаю объект, в котором включаю polling - это клиент-серверная технология, которая позволяет нам получать обновления с серверов телеграма. Если пользователь что-то написал боту, мы должны об этом как-то узнать и для этого мы будем с определенной периодичностью опрашивать сервер на предмет наличия новых действий пользователя с ботом. Polling можно просто включить указав ему значение true, но его можно настроить передав в значение объект с настройками, например: В данном примере я установил интервал между запросами с клиента на сервер в миллисекундах. autoStart отвечает за то, что наш бот отвечает на те сообщения, которые он пропустил за то время, когда был выключен. Однако, у polling есть ещё настройки, например, можно передать в значение params объект с параметрами такими, как timeout. Также давайте добавим наш первый слушатель боту - обработаем ошибку polling'а, выведем в консоль сообщение ошибки, если она вообще будет: Стоит отметить, что получать наш бот информацию о действиях с ним может не только с помощью технологии polling, но и с помощью webhook. Далее обработаем сообщения от пользователей. Для этого сначала добавим слушатель текстового сообщения: В данном примере мы добавили слушатель типа 'text'. Он возвращает нам следующий объект: Данный объект мы получаем в переменную msg и выводим в консоль. Расскажу немного подробнее про то, какие данные возвращает этот объект и на что стоит обратить внимание. В переменной text содержится само сообщение, которое нам написал пользователь. message_id определяет id сообщения, благодаря чему мы сможем далее обратиться к этому сообщению в обработчике слушателя, а объекты from и chat содержат информацию о том, какой пользователь написал это сообщение и в каком чате - чаще всего, например, id чата и id пользователя будут совпадать, однако, пользователи могут добавить бота в чат и писать ему туда - это тоже стоит учесть. Также бывают случаи, когда имя и ник пользователя могут до нас не дойти через слушатель, например, если пользователь закрыл это дело настройками конфиденциальности в телеграме - это тоже стоит учесть, например, в случае если мы хотим как-то обращаться к пользователю по нику или имени от лица бота. Теперь мы принимаем какую-то информацию, когда пользователь пишет сообщение боту. Давайте с помощью этой информации построим примитивного эхо-бота - наш бот будет отвечать тем же текстом, что и написал нам пользователь. Для этого используем метод sendMessage, в который передадим id чата, в который мы будем отправлять сообщение, и содержание этого сообщения: Вот так это дело работает: Давайте немного усложним нашу конструкцию. Практически это смысла иметь не будет, но позволит нам разобрать ещё несколько методов для работы с ботом. Попробуем сначала выводить пользователю сообщение о том, что бот генерирует ответ на сообщение, а затем через 5 секунд бот будет удалять сообщение о генерации ответа и будет скидывать ответ, в нашем случае то же сообщение, что и скинул нам пользователь: В данном примере, мы скидываем пользователю сообщение о генерации и записываем ответ сервера на запрос отправки сообщения в переменную msgWait. Ответ сервера будет объектом того же вида, что и объект msg. Далее, через пять секунд, используем метод deleteMessage для удаления сообщения о генерации и скидываем сам ответ. Попробуем немного изменить это дело. Вместо удаления сообщения и отправки нового сделаем отправку сообщения о генерации, а затем через 5 секунд отредактируем это сообщение на наш ответ: Для этого мы используем метод editMessageText, в который передаём строку на которую изменим наше сообщение, а также объект с данными о том, в каком чате и какое именно сообщение надо изменить. Также хочу обратить Ваше внимание на то, что по-хорошему необходимо все наши методы обернуть в конструкцию try/catch, потому что возможен вариант, при котором наш код отлично отрабатывает, всё хорошо написано, однако, пользователь заблокировал бота, и если бот попытается ему написать сообщение, то сервер нам вернёт ошибку и весь бот может крашнуться. Далее поговорим про обработку запуска бота. Каждый раз, когда новый пользователь заходит в бота перед ним появляется кнопка "Запустить", вот так это выглядит: Нажав эту кнопку пользователь отправит боту текстовую команду "/start". Сразу встаёт вопрос о том, как обработать это дело, чтобы поприветствовать нового пользователя и возможно записать какую-то информацию о пользователе в базу данных. Конечно, это можно сделать банальным способом и в слушателе текстового сообщения проверять, является ли сообщение пользователя текстовой командой "/start". Выглядеть это будет так: Однако, у этого способа есть один минус, с которым лично я столкнулся. В ссылку на запуск бота мы можем передавать параметры и потом читать их. Это полезно, если мы, допустим, захотим сделать реферальную систему. Давайте я покажу, как это будет выглядеть, и в чем проблема. Для этого сделаем обработчик ещё одной команды, назовём её "/ref" и будем по этой команде отдавать пользователю уникальную ссылку на запуск бота: Обратите внимание, для того чтобы передать какую-то информацию при запуске бота, я передаю в ссылку на запуск бота get-параметр с id пользователя, который получил эту ссылку.  Теперь, когда если мы перейдем по ссылке, у нас будет та же самая кнопка "Запустить", которая отправит "/start" боту. Однако, пользователь видит, что он отправил команду "/start", но нам в боте возвращается уже немного другой объект, выглядеть он будет так: В текст сообщения нам возвращается уже не просто "/start", а вместе с ним то, что мы передали в get-параметр в ссылке на запуск. Следовательно, нам надо кое-что изменить в обработчике команды "/start". Как видите, сначала мы изменили проверку с равенства текста сообщения команде "/start", на проверку, начинается ли текст сообщения с команды "/start". Затем проверяем есть ли ещё какие-то параметры в команде запуска, проверяя длину сообщения (6 в данном случае это длина строки "/start"), а затем вырезаем из текста сообщения команду "/start" вместе с пробелом после неё методом slice и записываем то, что мы передаём в ссылке на запуск бота в переменную. Сделать это можно и другим способом, используя слушатель onText и регулярные выражения: Слушатель принимает регулярное выражение, по которому будет проверять сообщение. Однако, если Вы будете применять данный способ, учтите, что если пользователь запустит бота, то сработает и слушатель onText с регулярным выражением, и слушатель on с типом 'text'. Поговорим о том, как создать меню команду для бота. Выглядит меню команд следующим образом: Создать это меню можно в BotFather или с помощью метода setMyCommands. Второй способ мне кажется удобнее и быстрее. В этот метод нам нужно передать массив объектов, в которых указаны сами команды и их описания в меню. Сначала мы задаём массив объектов с нашими командами, а затем передаём его в метод setMyCommands. Я создал команды, которые мы уже использовали до этого, а также создал новую команду help, давайте обработаем её в слушателе: Далее разберемся в форматировании текста в сообщениях. Разберем на примере обработки команды "/help". Для форматирования, стилизации, текста можно использовать либо HTML-верстку, либо Markdown-верстку. Для этого необходимо передавать в строку сообщения текст с тегами, а также передать объект с параметром parse_mode в метод sendMessage. Выглядеть это будет примерно так: Тогда команда "help" будет выводить нам следующее: Вот список тегов с помощью которых Вы можете стилизовать текст в телеграм-ботах: HTML: <b> Текст </b> - Жирный текст <i> Текст </i> - Текст курсивом <code> Текст </code> - Текст, который можно скопировать нажатием на него 
 Текст 
 - Перечеркнутый текст <u> Текст </u> - Подчеркнутый текст <pre language='язык'> Текст </pre> - Текст с оформлением кода <a href='ссылка'> Текст </a> - Текст-гиперссылка  Markdown: *Текст* - Жирный текст _Текст_ - Текст курсивом `Текст` - Текст, который можно скопировать нажатием на него ~Текст~ - Перечеркнутый текст ``` Текст ``` - Текст с оформлением кода || Текст || - Скрытый текст [Текст](Ссылка) - Текст-гиперссылка Стоит отметить, что все теги должны быть обязательно закрыты, иначе бот не отправит сообщение и вернёт ошибку. В этом плане, я советую делать стилизацию именно с помощью HTML-тегов, так как могут быть проблемы, если вы делаете админку или взаимодействие пользователей со стилизованным текстом. Например, если закинуть ссылку обычным текстом, а не гиперссылкой в сообщение, у которого parse_mode стоит на Markdown, то все нижние подчеркивания будут именно тегами, не отобразятся пользователю, и если их нечетное количество, то сообщение вообще не отправится. Также обратите внимание на то, что переход на следующую строку выполняется при помощи "\n" и в стилизации HTML, и в стилизации Markdown, здесь нельзя использовать тег <br> из HTML. Также если мы хотим вставить эмодзи в наше сообщение, то можно просто скопировать эмодзи из телеграмма и вставить в нашу строку в коде, например: Также ещё подробнее поговорим про ссылки. Если мы укажем ссылку в сообщении, то сообщение придёт пользователю с превью ссылки. Добавлю для этого команду "/link" и её обработчик. Выглядит это так:  Если мы хотим убрать превью в сообщении, то нам необходимо передать в метод sendMessage объект с параметром disable_web_page_preview со значением true: Кстати, в этот же объект, помимо parse_mode и disable_web_page_preview, мы можем передать параметр disable_notification - это позволит отправить сообщение пользователю без уведомления: Далее обсудим меню-клавиатуру. Меню-клавиатуры делятся на два типа: то меню, которое находится рядом с вводом текста, и меню, которое привязано к сообщению. Между ними есть некоторая разница, о которой мы поговорим дальше. Давайте для начала попробуем создать меню, которое не привязано к сообщению - для удобства я буду его дальше называть просто клавиатура. Для этого создадим ещё одну команду "menu" и обработаем её: В объект, в который мы раньше передавали параметры disable_web_page_preview, disable_notification, parse_mode теперь передаём reply_markup, который содержит массив массивов keyboard. Обратите внимание, что каждый массив в keyboard - это отдельная строка сверху вниз. То есть мы задаём массивами строки меню, а внутри строк задаём сами кнопки с помощью строк. Выглядеть то, что написано выше, в боте будет так: Можем заметить, что кнопки получились какими-то большими. Для того, чтобы задать им адекватные размеры, можно в объект reply_markup передать параметр resize_keyboard со значением true. Давайте так и сделаем, и добавим в меню ещё несколько кнопок: Далее встаёт вопрос о том, как обработать нажатие кнопки в нашей клавиатуре. Тут всё на самом деле просто. Когда пользователь нажимает на кнопку в меню, он скидывает боту текстовое сообщение с тем текстом, который написан на кнопке, который мы указывали в строках внутри массивов. Следовательно, будем обрабатывать нажатие на кнопку, как обычное текстовое сообщение: В данном примере, мы обработали нажатие кнопки закрытия меню. Обратите внимание на то, что если Вы просто отправите сообщение пользователю, клавиатура у него никуда не пропадёт. Для того, чтобы выключить клавиатуру, передаём параметр remove_keyboard со значением true в reply_markup. Как видим, клавиатура у нас пропала: О том, как создать меню привязанное к сообщению поговорим чуть дальше. Сейчас поговорим о том, как скинуть пользователю картинку, и как обработать сообщение с картинкой от пользователя. Давайте будем скидывать картинку пользователю по нажатию кнопки "⭐️ Картинка" в меню. Саму кнопку мы уже создали, давайте обработаем её нажатие и с помощью метода sendPhoto, в который передадим ссылку на изображение, которое хотим скинуть пользователю: В данном случае, мы скидываем пользователю картинку с помощью ссылки на изображение, однако, мы можем скинуть картинку и просто указав путь до картинки: Также можно скинуть картинку используя модуль fs: Теперь наш бот будет вести себя примерно так: Бот скидывает только картинку. Однако, нам может понадобится добавить какую-то подпись к этой картинке. Для этого передаем в метод sendPhoto объект с опциями, надпись можно передать в параметр caption, также можем задать parse_mode, как и в обычном текстовом сообщении: Далее разберемся с тем, как обработать сообщение с изображением от пользователя. Для этого используем слушатель с типом "photo": Теперь, когда пользователь скинет сообщение с картинкой, телеграм вернёт нам объект следующего вида: Мы похожее уже видели, когда обрабатывали текстовое сообщение. Только теперь мы имеем дело не со строкой text, а с массивом объектов photo. В этом массиве содержится наша картинка. Телеграм принял картинку, хранит у себя на сервере, и вернул нам несколько вариантов нашей картинки в разных размерах. Последний объект в этом массиве - это наш оригинал, а другие объекты содержат нашу картинку, только в сжатом виде. Но как же получить картинку, которую скидывал пользователь по информации содержащейся в объектах? Мы можем получить доступ к нашей картинке по file_id, который возвращает нам телеграм. Например, мы можем скачать файл по file_id с серверов телеграм используя метод downloadFile, в который передаём file_id, который нужно скачать, и директорию в которую будем скачивать файл. Выглядеть это будет так: Обратите внимание, что для того, чтобы выбрать наш оригинал в индексе массива я указал длину массива минус один - не будем забывать, что мы, программисты, считаем с нуля. Давайте теперь сделаем следующее: когда пользователь скидывает нам изображение, будем скидывать ему в ответ несколько изображений объединенных в одно сообщение. Эти несколько изображений - это наш оригинал и сжатые его варианты. Для этого воспользуемся методом sendMediaGroup: Остановимся на этом поподробнее. В самом начале мы инициализируем массив. Он нам нужен для того, чтобы передать несколько картинок в метод sendMediaGroup и соответственно скинуть эти картинки пользователю. Метод sendMediaGroup принимает массив объектов, в которых мы указываем первым делом тип медиа-контента, в нашем случае это "photo". Также в объекте медиа-контента мы должны передать путь до нашего контента, этим путём могут служить, также как и в случае с методом sendPhoto: url, stream, buffer или путь до контента. Также мы можем указать file_id - его можно тоже указывать при отправке через sendPhoto или через sendMediaGroup, однако, в данном случае этот способ нам не подходит, так как какой бы file_id мы не указали, мы всегда будем скидывать оригинал картинки, а нас интересуют именно сжатые картинки. Дополнительно в объект медиа-контента мы можем передать caption - подпись под картинкой. В данном случае, я добавил в подпись под каждой картинкой её размер в байтах, ширину и высоту в пикселях - всё это нам возвращает телеграм. Теперь пройдясь циклом по всем вариантам нашей картинки, скачав все варианты и записав всю необходимую информацию, передаём наш массив объектов в метод sendMediaGroup, а затем удаляем все картинки, которые только что скачали. И теперь имеем следующее: Далее поговорим о том, как скинуть видео. Тут в общем-то всё аналогично фото, обработаем нажатие на кнопку на "⭐️ Видео" в нашем меню: Также мы туда можем добавить caption и parse_mode, и другие параметры, вроде disable_notification: Получаем следующее: Теперь давайте обработаем сообщение пользователя с видео. Делаем это также с помощью слушателя с типом "video": Телеграм присылает нам следующее, когда пользователь отправляет сообщение с видео: Как видим, телеграм предоставляет нам информацию о видео и изображениях. Изображения - это миниатюры видео. Давайте сделаем следующее: когда пользователь скидывает видео, бот будет в ответ кидать само видео, миниатюру и информацию о видео. Выглядеть это будет следующим образом: Обратите внимание, что видео-файл я скидываю по его file_id, а миниатюру скачиваю, скидываю и затем удаляю, потому что телеграм не даёт скинуть миниатюру методом sendPhoto по file_id. Также важно, что в этот раз я добавил в методе sendMediaGroup параметр caption лишь к одному медиа-файлу и надпись отобразилась в сообщении. Также есть важный момент, который нужно учесть. Иногда, когда пользователь скидывает видео, допустим в том же .mp4, телеграм может это сжать и скинуть в .gif. Ситуация уже знакомая нам, скидываем аудио также, как видео и фото, обрабатывая кнопку "⭐️ Аудио": Обработка аудио выполняется тоже похожим образом, только используем слушатель с типом "audio": Телеграм-сервер возвращает похожий на предыдущие примеры объект: Хочу обратить внимание на то, что если пользователь скинет несколько видео, фото или аудио одним сообщением, то они не вернутся Вам одним сообщением с массивом audio или video-объектов, просто сработают несколько слушателей и эти файлы будут считаться отдельными сообщениями и обрабатываться будут также отдельно. С голосовыми ситуация аналогичная, ничего интересного: И также обрабатываем голосовые сообщения: Дальше разберемся с тем, как скинуть пользователю контакт. Для этого обработаем кнопку "⭐️ Контакт", используя метод sendContact, в который передадим строку с номером телефона и именем контакта: Обратите внимание, что в телеграме стоит проверка на формат номера, и если мы укажем не номер, то телеграм вернёт нам ошибку. Также я добавил параметр reply_to_message_id - в этот параметр мы можем передать message_id сообщения, на которое мы хотим ответить сообщением, в которое передаем message_id. Теперь давайте немного изменим нашу кнопку. И вместо того, чтобы просто скидывать контакт, мы сначала будем его запрашивать, а затем обрабатывать. Для этого изменим обработку команды "/menu", в которой мы присылаем пользователю нашу клавиатуру: Как видите, теперь передаём вместо просто строки '⭐️ Контакт' объект с параметрами text и request_contact, тем самым запрашивая контакт пользователя при нажатии на кнопку: Теперь обработаем контакт. Для этого добавляем слушатель с типом "contact". Этот слушатель вернёт нам следующий объект, если пользователь скинул контакт: Давайте обработаем это следующим образом: когда боту приходит контакт, бот скидывает номер и имя контакта, отвечая на сообщение с контактом: Выглядеть это будет так: Обработаем нажатие кнопки "⭐️ Геолокация" и будем скидывать пользователю геолокацию Красной Площади, указав широту и долготу нужной нам координаты: Теперь провернём тоже самое, что мы делали с контактами, только для геолокации - сделаем запрос геолокации по кнопке и будем возвращать в ответ на сообщение с геолокацией координаты геолокации (широту и долготу). Начнём с того, что изменим кнопку "⭐️ Геолокация": Далее обработаем сообщение пользователя с геолокацией. Сообщение с геолокацией возвращает следующий объект: Сделаем следующий слушатель: Выше я уже говорил, что меню можно создать нескольких видов. Выше мы уже создали меню-клавиатуру и меню команд. Теперь создадим меню, как я писал выше, привязанное к сообщению. Далее будем называть это инлайн-клавиатура. Для этого создадим новую команду и обработаем её: Как видим мы также, как с обычной клавиатурой, создаём массив массивов в объекте reply_markup. Каждый массив в inline_keyboard - это новая строка в меню. В отличие от обычной клавиатуры, мы передаём в массив не строки, а объекты с текстом и callback_data. Обрабатывать мы будем нажатия кнопок не обычным текстовым сообщением, а уже коллбеками. Вот так будет выглядеть созданная нами инлайн-клавиатура: Теперь давайте попробуем обработать кнопку "Закрыть Меню". Для этого будем использовать слушатель с типом "callback_query": Коллбеки возвращают нам с серверов телеграм следующий объект: Как видим, в объекте возвращается информация о сообщении, к которому привязана кнопка, на которую нажали, информация о пользователе, который нажал кнопку, и самое важное - callback_data, которую мы указывали в кнопке. С помощью вот этого параметра callback_data будем обрабатывать нажатие на кнопку: В данном случае, мы удаляем сообщение с нашей инлайн-клавиатурой по нажатию кнопки "Закрыть Меню". Дальше в этом же слушателе будем обрабатывать нажатия других кнопок по параметру callback_data. Теперь давайте немного модифицируем всё это дело. И наше меню будем скидывать не просто сообщением по команде, а в ответ на сообщение-команду: Теперь мы можем сделать следующее: в обработчике закрытия меню удалять не только сообщение с самим меню, а ещё и удалять сообщение-команду, по которому было вызвано меню: Мы можем такое сделать, потому что тогда наш коллбек будет возвращать следующий объект: Далее обработаем нажатие кнопки "Стикер" на нашей инлайн-клавиатуре. Для этого будем использовать метод sendSticker: Хочу обратить ваше внимание на то, что мы можем скинуть любое изображение стикером, аналогично тому, как мы это делали с методом sendPhoto. Теперь обработаем сообщение пользователя со стикером с помощью слушателя с типом "sticker", который будет возвращать нам следующий объект: Давайте теперь сделаем следующее: когда пользователь скидывает стикер, будем проверять, какого вида стикер используя информацию из объекта (булевы переменные) и будем скидывать стикер пользователю картинкой, видео или анимацией: Обратите внимание, что я сделал некрасиво: в любом случае, будь стикер анимацей, видео или картинкой скачиваю файл в папку с названием "image" - так лучше, конечно, не делать. Также я не отправляю файлы по file_id, а скачиваю и затем их отправляю, потому что если попробовать скинуть стикер-файл по file_id используя методы sendPhoto или sendVideo, телеграм вернёт ошибку о том, что нельзя скинуть файл типа "стикер", как "фото" или "видео", однако, метод sendAnimation позволяет отправлять стикеры анимацией по их file_id, тогда это будет выглядеть следующим образом: Обработаем кнопку "Круглое Видео", по нажатию на которую будем скидывать пользователю видео круглого формата. Сделать это можно с помощью метода sendVideoNote: В общем-то с круглыми видео ничего интересного нету - всё аналогично обычному видео. Кстати, в объект опций мы можем передать параметр protect_content со значением true и пользователь не сможет пересылать сообщение: Далее разберемся с тем, как сделать, чтобы бот проверял подписку на канал. Для этого необходимо поставить бота админом канала, на который он будет проверять подписку и использовать метод getChatMember: В этот метод передаём ID нашего канала или чата - получить этот ID можно, например, скинув ссылку на канал или чат в специального бота, который возвращает ID, он будет в формате "-100XXXXXXXXXX", и также передаём в этот метод ID пользователя, подписку которого будем проверять. Данный метод вернёт объект такого вида: Будем обращаться к статусу пользователя. Статусы бывают следующие: left - пользователь не подписан kicked - пользователь заблокирован member - пользователь подписан administrator - пользователь является администратором creator - пользователь является создателем Обработаем статус пользователя и в зависимости от него будем выводить пользователю сообщение: Для того, чтобы подключить оплату к своему бота для начала стоит определиться с платежной системой через которую будут совершаться платежи. У телеграма есть хороший выбор стандартных платежный систем, однако, никто не запрещает Вам подключить стороннюю платежную систему, но сейчас разберемся именно со стандартными телеграмовскими платежными системами. Для их подключения необходимо перейти в BotFather, выбрать своего бота, перейти в раздел Payments, а дальше в выбранной Вами платежной системе получить provider token - ключ, по которому мы будем подключать оплату.  Давайте отправим счёт на оплату пользователю по нажатию кнопки "Купить Файл". Для этого будем использовать метод sendInvoice: В этот метод мы передаём название платежа, описание платежа, payload - это информация, которая передаётся в платеж, по ней мы будем отслеживать нужный платеж, а у пользователя она нигде не отобразится, провайдер-токен, валюта (в разных платежках поддерживаются разные валюты) и также массив объектов с товарами, которые будет оплачивать пользователь. Обратите внимание, что валютой я указал рубли (код валюты в ISO 4217), а цену на товар указал в копейках. Также можно в метод передать другие параметры, можно добавить миниатюру платежа, включить необходимость ввода некоторых данных для пользователя, а также данные для фискализации платежа. Далее по кнопке "Купить Файл" пользователю отправится следующее сообщение: Далее пользователь нажмёт кнопку оплаты, введёт свои данные, и когда подтвердит введённые данные, бот должен отправить окончательное подтверждение перед оформлением заказа. Сделать это можно при помощи обработки такого слушателя: Когда мы окончательно подтвердили оформление заказа, пользователь может совершить платеж и мы должны его как-то обработать. Для этого будем использовать слушатель с типом "successful_payment", который возвращает объект следующего вида: Исходя из этой информации мы можем обработать совершённый платеж, например: Используем метод sendDocument для того, чтобы скинуть пользователю файл, который мы находим благодаря информации из payload, и в подписи к нему выводим информацию из payload, которую мы изначально передали в методе sendInvoice. Пользователь увидит следующее: В этом материале постарался максимально просто и с примерами рассказать об основных возможностях телеграм-ботов и как ими управлять с помощью NodeJS. Тут в принципе есть ещё о чем рассказать: веб-хуки для телеграм ботов, управление чатами, каналами, игры и веб-приложения в телеграм-ботах. Если кому-то будет интересна эта тема, то продолжу об этом писать. Также выложил код всего, что было написано в этой статье на GitHub с комментариями, посмотреть и скачать можно здесь.  JS-Разработчик