Запуск Node.js скрипта на сервере [дубликат]
Здравствуйте
Сделал простого ВК бота на NodeJS. Запускаю бота через командную строку, например:
node bot.js
Если прервать работу скрипта (Ctrl + C) или закрыть консоль, то соответственно, бот больше не работает.
Нужно чтобы бот работал все время. Есть хостинг, но как сделать так, чтобы этот скрипт запускался на хостинге? Заливал файлы на сервер — скрипт не запускался. Менял название скрипта на index.js, тоже не работает.
Отслеживать
задан 25 сен 2017 в 17:36
225 1 1 золотой знак 4 4 серебряных знака 15 15 бронзовых знаков
Создание бота Discord Bot с помощью Node.js
Discord — это приложение чата, позволяющее миллионам пользователей со всего мира обмениваться сообщениями и общаться в голосовом чате в сообществах, называемых «гильдии» или «серверы». Discord также включает функциональный API, который разработчики смогут использовать для построения мощных ботов Discord. Боты могут выполнять разнообразные действия, в том числе отправлять сообщения на серверы, отправлять пользователям сообщения DM, модерировать серверы и воспроизводить звук в голосовых чатах. Это позволяет разработчикам создавать мощных ботов со сложными и продвинутыми функциями, включая инструменты модерирования и даже игры. Например, служебный бот Dyno обслуживает миллионы гильдий и содержит множество полезных функций, включая защиту от спама, музыкальный проигрыватель и другие служебные функции. Научившись создавать боты Discord, вы получите множество возможностей, с которыми тысячи людей смогут взаимодействовать каждый день.
В этом обучающем модуле мы создадим бот Discord с нуля, используя Node.js и библиотеку Discord.js, позволяющую пользователям взаимодействовать с Discord API напрямую. Мы настроим профиль бота Discord, получим токены аутентификации для бота и запрограммируем бот для обработки отправляемых пользователями команд с аргументами.
Предварительные требования
Для начала вам потребуется следующее:
- Node.js, установленный на вашем компьютере для разработки. Чтобы установить его в macOS или Ubuntu 18.04, следуйте указаниям руководства Установка Node.js и создание локальной среды разработки в macOS или раздела Установка с помощью PPA руководства Установка Node.js в Ubuntu 18.04.
- Любой текстовый редактор по вашему усмотрению, например, Visual Studio Code, Atom, Sublime или Nano.
- Бесплатная учетная запись Discord с подтвержденной учетной записью электронной почты и бесплатный сервер Discord, который вы будете использовать для тестирования вашего бота Discord.
Шаг 1 — Настройка бота Discord
На этом шаге вы будете использовать графический интерфейс разработчиков Discord для настройки бота Discord и получения токена бота, который вы передадите в свою программу.
Чтобы зарегистрировать бота на платформе Discord, используйте панель приложений Discord. Здесь разработчики могут создавать приложения Discord, включая ботов Discord.

Для начала нажмите New Application (Новое приложение). Discord предложит вам ввести имя нового приложения. Затем нажмите Create (Создать) для создания приложения.

Примечание. Имя приложения не связано с именем бота, и эти имена могут не совпадать.
Теперь откройте свою панель приложения. Чтобы добавить бота в приложение, откройте вкладку Bot (Бот) на панели навигации слева.

Нажмите кнопку Add Bot (Добавить бота), чтобы добавить бота в приложение. Нажмите кнопку Yes, do it! (Да, сделать это!) в диалоге запроса подтверждения. Откроется панель с подробными сведениями об имени вашего бота, токене аутентификации и изображении в профиле.

На этой панели вы можете изменить имя бота или изображение профиля. Также необходимо скопировать токен аутентификации бота, нажав Click to Reveal Token (Нажмите для показа токена) и скопировав отображаемый токен.
Предупреждение. Никогда не публикуйте и не выгружайте в сеть свой токен бота, поскольку это позволит любому пользователю выполнить вход в бот.
Теперь нужно создать приглашение, которое позволяет добавить гильдии ботов Discord, где вы можете протестировать бот. Вначале перейдите на вкладку OAuth2 на панели приложения. Чтобы создать приглашения, прокрутите страницу и выберите бота в разделе scopes. Также необходимо установить разрешения, чтобы определить, какие действия бот может выполнять в гильдиях. Для целей этого обучающего модуля выберите пункт Administrator (Администратор), которые дадут вашему боту разрешения для выполнения в гильдиях практически любых действий. Скопируйте ссылку с помощью кнопки Copy.

Затем добавьте бот на сервер. Перейдите по созданной ссылке с приглашением. Вы можете добавить бот на любой сервер, который вам принадлежит или на котором у вас есть права администратора, используя выпадающее меню.

Теперь нажмите кнопку Continue (Продолжить). Обязательно установите отметку рядом с полем Administrator, чтобы предоставить боту разрешения администратора. Затем нажмите Authorize (Авторизация). Discord предложит вам решить головоломку CAPTCHA, прежде чем бот сможет подключиться к серверу. Теперь наш бот Discord отображается в списке пользователей сервера, на который вы добавили бота, со статусом offline (не в сети).

Вы успешно создали бота Discord и добавили его на сервер. Далее мы напишем программу для входа в бот.
Шаг 2 — Создание проекта
На этом шаге мы настроим базовую среду программирования, где выполним сборку бота и войдем в него программным путем.
Вначале необходимо настроить папку проекта и необходимые файлы проекта для бота.
Создайте папку проекта:
Перейдите в только что созданную папку проекта:
Далее используйте текстовый редактор для создания файла с именем config.json для сохранения токена аутентификации вашего бота:
Затем добавьте в файл конфигурации следующий код, заменяя выделенный текст токеном аутентификации вашего бота:
config.json
"BOT_TOKEN": "YOUR BOT TOKEN" >
Сохраните и закройте файл.
Затем мы создадим файл package.json , где будут храниться детали нашего проекта и информация о зависимостях, используемых в проекте. Для создания файла package.json запустите следующую команду npm :
Сейчас мы установим пакет discord.js , который будем использовать для взаимодействия с Discord API. Вы можете установить discord.js через npm с помощью следующей команды:
Мы настроили файл конфигурации, установили необходимую зависимость и теперь можем начать сборку бота. В реальном приложении мы бы разделили код большого бота на несколько файлов, но в этом обучающем модуле мы поместим весь код бота в один файл.
Вначале создайте файл с именем index.js в папке discord-bot для кода:
Начните программирование бота, запросив зависимость discord.js и файл конфигурации с токеном бота:
const Discord = require("discord.js"); const config = require("./config.json");
После этого добавьте следующие две строки кода:
. const client = new Discord.Client(); client.login(config.BOT_TOKEN);
Сохраните и закройте файл.
Первая строка кода создает новый клиент Discord.Client и назначает его как значение константы client . Этот клиент частично обеспечивает взаимодействие с Discord API и получает уведомления Discord о событиях, например, о новых сообщениях. Фактически клиент представляет бот Discord.
Во второй строке кода метод login используется на клиенте для входа в созданный бот Discord с использованием токена в файле config.json в качестве пароля. Токен сообщает Discord API, для какого бота предназначена программа, а также подтверждает вашу аутентификацию для использования бота.
Теперь запустите файл index.js с помощью Node:
Теперь статус вашего бота на сервере Discord, куда вы его добавили, изменится на online (в сети).

Мы успешно настроили среду программирования и создали базовый код для входа в бот Discord. На следующем шаге мы займемся пользовательскими командами и заставим бота выполнить определенные действия, например, отправить сообщения.
Шаг 3 — Обработка первой команды пользователя
На этом шаге мы создадим бот, который может обрабатывать команды пользователя. Вы запустите свою первую команду ping , на которую будет отправлен ответ pong с указанием времени ответа на команду.
Сначала необходимо обеспечить обнаружение и получение любых отправляемых пользователями сообщений, чтобы бот мог обрабатывать любые команды. Используя метод on клиента Discord, Discord будет отправлять вам уведомления о новых событиях. Метод on принимает два аргумента: название ожидаемого события и функцию, которая будет запускаться каждый раз, когда будет возникать это событие. С этим методом вы можете ожидать события message , которое будет возникать каждый раз при отправке сообщения в гильдию, где у бота имеется разрешение на просмотр сообщений. Поэтому мы создадим функцию, которая будет запускаться для обработки команд каждый раз при отправке сообщения.
Вначале откройте свой файл:
Добавьте в файл следующий код:
. const client = new Discord.Client(); client.on("message", function(message) >); client.login(config.BOT_TOKEN);
Эта функция, выполняемая для события message , принимает параметр message . Параметр message имеет значение экземпляра Discord.js message, содержащее информацию об отправленном сообщении и методах, которые помогут боту ответить на него.
Добавьте следующую строку кода в функцию обработки команд:
. client.on("message", function(message) if (message.author.bot) return; >); .
Эта строка проверяет, является ли автор сообщения ботом, и если это так, останавливает обработку команды. Это важно, поскольку обычно нам не нужно обрабатывать сообщения ботов или отвечать на них. Другим ботам обычно не требуется использовать наш бот, и поэтому игнорирование их сообщений позволит нам сэкономить вычислительную мощность и предотвратить случайные ответы.
Теперь мы напишем обработчик команд. Для этого важно понимать обычный формат команды Discord. Обычно структура команды Discord содержит три части в следующем порядке: префикс, имя команды и (иногда) аргументы команды.

- Префикс: префикс может быть любым, но обычно это знак пунктуации или абстрактная фраза, которая обычно не может располагаться в начале сообщения. Это означает, что при добавлении префикса в начало сообщения бот будет знать, что это команда, которую ему необходимо обработать.
- Имя команды: имя команды, которое пользователь хочет использовать. Это означает, что бот может поддерживать несколько команд с разными функциями и разрешать пользователям выбирать между ними, используя разные имена команд.
- Аргументы: иногда команда запрашивает или использует дополнительную информацию от пользователя, и пользователь может указывать аргументы после имени команды, разделяя их пробелами.
Примечание. Принудительно устанавливаемой структуры команд нет, и боты могут обрабатывать команды как угодно, однако здесь мы описываем эффективную структуру, которую использует большинство ботов.
Чтобы начать создание синтаксического анализатора команд, поддерживающего этот формат, добавьте следующие строки кода в функцию обработки сообщений:
. const prefix = "!"; client.on("message", function(message) if (message.author.bot) return; if (!message.content.startsWith(prefix)) return; >); .
Первая строка кода добавляется, чтобы присвоить значение «!» константе prefix , которую мы будем использовать в качестве префикса бота.
Вторая строка кода проверяет, начинается ли содержание обрабатываемого ботом сообщения с заданного префикса, и останавливает дальнейшую обработку сообщения, если префикса нет.
Теперь необходимо конвертировать остальную часть сообщения в название команды и любые аргументы, которые могут присутствовать в сообщении. Добавьте следующие выделенные строки:
. client.on("message", function(message) if (message.author.bot) return; if (!message.content.startsWith(prefix)) return; const commandBody = message.content.slice(prefix.length); const args = commandBody.split(' '); const command = args.shift().toLowerCase(); >); .
Первая строка используется для удаления префикса из содержания сообщения и назначения результата константе commandBody . Это необходимо, поскольку нам не нужно, чтобы префикс входил в обработанное имя команды.
Вторая строка принимает сообщение с удаленным префиксом и использует на нем метод split с пробелом в качестве разделителя. Он разделяет команду на массив субстрок, проводя разделение после каждого пробела. В результате получается массив, содержащий имя команды, за которым идут аргументы (если они содержатся в сообщении). Мы назначаем этот массив константе args .
Третья строка удаляет первый элемент из массива args (это будет имя команды), конвертирует его в нижний регистр и назначает константе command . Это позволяет изолировать имя команды и оставить в массиве только аргументы. Также мы используем метод toLowerCase , поскольку команды в ботах Discord обычно не учитывают регистр.
Мы завершили построение синтаксического анализатора команд с реализацией обязательного префикса и получением имени команды и всех аргументов из сообщений. Теперь мы реализуем и создадим код для определенных команд.
Добавьте следующий код, чтобы начать реализацию команды ping :
. const args = commandBody.split(' '); const command = args.shift().toLowerCase(); if (command === "ping") > >); .
Это выражение if проверяет соответствие обработанного имени команды (назначенного константе command ) значению «ping» . Такое соответствие означает, что пользователь хочет использовать команду «ping» . Мы вложим код определенной команды в блок выражения if . Впоследствии вы сможете использовать этот шаблон для других команд.
Теперь вы можете реализовать код для команды «ping» :
. if (command === "ping") const timeTaken = Date.now() - message.createdTimestamp; message.reply(`Pong! This message had a latency of $timeTaken>ms.`); > .
Сохраните и закройте файл.
Мы добавляем блок команды «ping» , который рассчитывает разницу между текущим временем, определяемым с помощью метода now объекта Date , и временной меткой создания сообщения, выражаемую в миллисекундах. Таким образом рассчитывается время обработки сообщения и ответа бота на команду «ping» .
Вторая строка отвечает на команду пользователя, используя метод reply с константой message . Метод reply отправляет запрос ping (уведомляющий пользователя и выделяющий сообщение для указанного пользователя) пользователю, который отправил команду, добавляя после запроса содержание, указанное в качестве первого аргумента метода. Мы указали литераль шаблона, содержащую сообщение и рассчитанное значение ping, в качестве ответа, который будет использоваться методом reply .
На этом мы завершаем реализацию команды «ping» .
Запустите свой бот с помощью следующей команды (в той же папке, что и index.js ):
Теперь вы можете использовать команду «! ping» на любом канале, где бот может просматривать и принимать сообщения, а также отправлять ответы.

Вы успешно создали бот, который может обрабатывать команды пользователей, и реализовали свою первую команду. На следующем шаге мы продолжим разработку бота, реализовав команду sum.
Шаг 4 — Реализация команды Sum
Теперь мы расширим нашу программу, реализовав команду «! sum» . Эта команда принимает любое количество аргументов, складывает их и возвращает пользователю сумму всех аргументов.
Если ваш бот Discord еще работает, вы можете остановить его процессы, нажав CTRL + C .
Откройте файл index.js снова:
Чтобы начать реализацию команды «! sum» , мы используем блок else-if . После проверки имени команды ping бот будет проверять, имеет ли имя команды значение «sum» . Мы используем блок else-if , потому что одновременно будет обрабатываться только одна команда, и если программа определит совпадение с именем команды «ping» , ей не нужно будет проводить проверку на совпадение с именем команды «sum» . Добавьте в файл следующие выделенные строки:
. if (command === "ping") const timeTaken = Date.now() - message.createdTimestamp; message.reply(`Ping! This message had a latency of $timeTaken>ms.`); > else if (command === "sum") > >); .
Теперь вы можете начать реализацию кода для команды «sum» . Код команды «sum» будет размещен в только что созданном нами блоке else-if . Теперь добавьте следующий код:
. else if (command === "sum") const numArgs = args.map(x => parseFloat(x)); const sum = numArgs.reduce((counter, x) => counter += x); message.reply(`The sum of all the arguments you provided is $sum>!`); > .
Мы используем метод map для списка аргументов, чтобы создать новый список, используя функцию parseFloat для каждого элемента в массиве args . При этом создается новый массив (назначенный константе numArgs ), где все элементы являются числами, а не строками. Это означает, что в дальнейшем вы можете определить сумму чисел, выполнив операцию сложения.
Вторая строка использует метод reduce на константе numArgs , предоставляя функцию, суммирующую все элементы в списке. Мы назначаем сумму всех элементов в numArgs как значение константы sum .
Затем мы используем метод reply на объекте сообщений, чтобы ответить на команду пользователя, отправив литераль шаблона, содержащую сумму всех аргументов, отправленных пользователем боту.
Это завершает реализацию команды «sum» . Теперь запустите бот, используя следующую команду (в той же папке, что и index.js ):
Теперь вы можете использовать команду «! sum» на любом канале, где бот может просматривать и отправлять сообщения.

Далее приведена законченная версия скрипта бота index.js :
const Discord = require("discord.js"); const config = require("./config.json"); const client = new Discord.Client(); const prefix = "!"; client.on("message", function(message) if (message.author.bot) return; if (!message.content.startsWith(prefix)) return; const commandBody = message.content.slice(prefix.length); const args = commandBody.split(' '); const command = args.shift().toLowerCase(); if (command === "ping") const timeTaken = Date.now() - message.createdTimestamp; message.reply(`Pong! This message had a latency of $timeTaken>ms.`); > else if (command === "sum") const numArgs = args.map(x => parseFloat(x)); const sum = numArgs.reduce((counter, x) => counter += x); message.reply(`The sum of all the arguments you provided is $sum>!`); > >); client.login(config.BOT_TOKEN);
На этом шаге мы дополнили наш бот Discord, реализовав в нем команду sum .
Заключение
Мы успешно создали бот Discord, который может обрабатывать разные команды пользователей и аргументы команд. Если вы хотите расширить возможности вашего бота, вы можете реализовать дополнительные команды или использовать другие части Discord API для построения мощного бота Discord. Вы можете ознакомиться с документацией по Discord.js или Discord API, чтобы узнать больше о Discord API.
При создании ботов Discord всегда помните об условиях обслуживания Discord API, определяющих требования к использованию Discord API разработчиками. Также вам может быть полезен этот набор рекомендаций, показывающий наилучшие способы построения бота Discord и содержащий советы по проектированию ботов Discord. Если вы хотите узнать больше о Node.js, ознакомьтесь с нашей серией материалов «Программирование в Node.js».
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
Телеграм-бот на NodeJS. 1. Создать бота.
В серии заметок я расскажу как написал телеграмм-бота на Node.JS и развернул его на амазоне.
Вообще считается что я фронт-энд разработчик, поэтому возможно те слова, что я буду здесь писать, не смогут 100% ответить на ваши вопросы, а возможно даже будут замечания по тому, как реализованы блоки кода. Ну, если что- всегда доступны комментарии, пишите, обсудим. Итак, первый шаг по созданию бота простой- нужно собственно создать этот самый бот. Делается это в телеграмм-боте @botfather. После того, как Вы добавите его себе в контакты, дайте ему команду /help Дайте имя боту — клиенты увидят это имя при общении с ботом. И никнейм бота — по нему можно будет найти бота в Telegram. Никнейм должен быть уникальным, не повторять существующие в базе и заканчиваться на слово «bot»
После того как вы выберите подходящее имя бот будет создан. Вы получите сообщение со ссылкой на бота t.me/, рекомендации по настройке аватарки, описание бота и список команд для настройки бота.
Поздравляю Вас с созданием своего телеграмм-бота и себя за то, что я смог Вас научить такому действию. На этом в принципе можно было остановиться, но нет- какая же это работа, а где заявленный NodeJS? — Давайте приступим к оживлению бота.
Создание логики работы бота на NodeJS
Перед этим нам будет необходимо сделать кое-какие приготовления, а именно: Создать проект и установить пакет telegraf:
npm init -y npm i telegraf --save
Enter fullscreen mode
Exit fullscreen mode
Теперь осталось дело за малым: написать его действия.
Создайте JS-файл bot.js такого содержания:
const Telegraf > = require("telegraf"); // Создать бота с полученным ключом const bot = new Telegraf("1496911419:AAGt2IUR7zC2-5 1VTLj4pLzt3i7l664-P0x0"); // Обработчик начала диалога с ботом bot.start((ctx) => ctx.reply( `Приветствую, $ ctx.from.first_name ? ctx.from.first_name : "хороший человек" >! Набери /help и увидишь, что я могу.` ) ); // Обработчик команды /help bot.help((ctx) => ctx.reply("Справка в процессе")); // Обработчик команды /whoami bot.command("whoami", (ctx) => const id, username, first_name, last_name > = ctx.from; return ctx.replyWithMarkdown(`Кто ты в телеграмме: *id* : $id> *username* : $username> *Имя* : $first_name> *Фамилия* : $last_name> *chatId* : $ctx.chat.id>`); >); // Обработчик простого текста bot.on("text", (ctx) => return ctx.reply(ctx.message.text); >); // Запуск бота bot.launch();
Enter fullscreen mode
Exit fullscreen mode
Что у нас тут написано:
const bot = new Telegraf("1496911419:AAGt2IUR7zC2-1VTLj4pLzt3i7l664-P0x0");
Enter fullscreen mode
Exit fullscreen mode
Здесь тот самый ключ, который нам поведал BotFather . А т.к. этот ключ не только полезен, но и весьма важен, то его я бы скрыл от тех, кто будет смотреть наш код. Например- на гитхабе. Поэтому давайте напишем эту строку по-другому:
const bot = new Telegraf(process.env.TELEGRAM_TOKEN_EDU);
Enter fullscreen mode
Exit fullscreen mode
А сам код запишем в файле .env :
TELEGRAM_TOKEN_EDU="1496911419:AAGt2IUR7zC2-1VTLj4pLzt3i7l664-P0x0"
Enter fullscreen mode
Exit fullscreen mode

Для того, чтобы сработал этот код нужно установить пакет dotenv и в начале файла bot.js написать: require(«dotenv»).config(); Дальше по коду можно будет из комментариев понять, что делают команды. Запустить наш скрипт можно командой node bot.js и в телеграмм-чате с ботом у Вас должно быть примерно следующее:
Сейчас самое время поэкспериментировать — Добавить свои команды; изменить bot.on(«text», . например чтобы бот не просто отображал вводимый текст, а как-то его менял- придумывал рифму или менял порядок букв.
В следующей части научим бот показывать картинку.
Телеграм-боты на NodeJS
Несколько месяцев назад как-то больше по приколу написал телеграм-бота с интеграцией GPT. Это было, кстати, ещё до того, как весь телеграм утонул в этих ботах. После этого решил, что можно попробовать эту область на фрилансе. За эти месяцы сделал миллион всяких телеграм-ботов с GPT, другими нейронками с доступным API (и даже недоступным в случае с Midjourney), всякие магазины и тому прочих ботов. Этот опыт позволил прошариться немного за телеграм-ботов и в этом материале расскажу об основных моментах, с которыми Вы скорее всего столкнётесь при написании телеграм-ботов на NodeJS. Если есть чем меня дополнить или, возможно, поправить, то буду рад обратной связи.
Начало работы
Для начала создадим NodeJS проект и установим туда пакет для работы с телеграм-ботом через npm:
npm init
npm i node-telegram-bot-api
И инициализируем библиотеку для работы с телеграм-ботом в проект:
const TelegramBot = require('node-telegram-bot-api');
Далее нам нужно создать экземпляр класса TelegramBot. В конструктор нам необходимо передать токен нашего бота (создать бота и получить токен для него можно в BotFather):
const bot = new TelegramBot(process.env.API_KEY_BOT, < polling: true >);
Я для хранения таких переменных, как токен бота, использую модуль dotenv, однако, это можно представить в следующем виде:
const API_KEY_BOT = 'Токен от Вашего бота'; const bot = new TelegramBot(API_KEY_BOT, < polling: true >);
Polling
Обратите внимание, что вместе с токеном бота я передаю объект, в котором включаю polling — это клиент-серверная технология, которая позволяет нам получать обновления с серверов телеграма. Если пользователь что-то написал боту, мы должны об этом как-то узнать и для этого мы будем с определенной периодичностью опрашивать сервер на предмет наличия новых действий пользователя с ботом. Polling можно просто включить указав ему значение true, но его можно настроить передав в значение объект с настройками, например:
const API_KEY_BOT = 'Токен от Вашего бота'; const bot = new TelegramBot(API_KEY_BOT, < polling: < interval: 300, autoStart: true >>);
В данном примере я установил интервал между запросами с клиента на сервер в миллисекундах. autoStart отвечает за то, что наш бот отвечает на те сообщения, которые он пропустил за то время, когда был выключен. Однако, у polling есть ещё настройки, например, можно передать в значение params объект с параметрами такими, как timeout.
Также давайте добавим наш первый слушатель боту — обработаем ошибку polling’а, выведем в консоль сообщение ошибки, если она вообще будет:
bot.on("polling_error", err => console.log(err.data.error.message));
Стоит отметить, что получать наш бот информацию о действиях с ним может не только с помощью технологии polling, но и с помощью webhook.
Обработка и отправка текстового сообщения
Далее обработаем сообщения от пользователей. Для этого сначала добавим слушатель текстового сообщения:
bot.on('text', async msg => < console.log(msg); >)
В данном примере мы добавили слушатель типа ‘text’. Он возвращает нам следующий объект:
< message_id: ID_СООБЩЕНИЯ, from: < id: ID_ПОЛЬЗОВАТЕЛЯ, is_bot: false, first_name: ИМЯ_ПОЛЬЗОВАТЕЛЯ, username: НИК_ПОЛЬЗОВАТЕЛЯ, language_code: 'ru' >, chat: < id: ID_ЧАТА, first_name: ИМЯ_ПОЛЬЗОВАТЕЛЯ, username: НИК_ПОЛЬЗОВАТЕЛЯ, type: 'private' >, date: 1686255759, text: ТЕКСТ_СООБЩЕНИЯ, >
Данный объект мы получаем в переменную msg и выводим в консоль. Расскажу немного подробнее про то, какие данные возвращает этот объект и на что стоит обратить внимание. В переменной text содержится само сообщение, которое нам написал пользователь. message_id определяет id сообщения, благодаря чему мы сможем далее обратиться к этому сообщению в обработчике слушателя, а объекты from и chat содержат информацию о том, какой пользователь написал это сообщение и в каком чате — чаще всего, например, id чата и id пользователя будут совпадать, однако, пользователи могут добавить бота в чат и писать ему туда — это тоже стоит учесть. Также бывают случаи, когда имя и ник пользователя могут до нас не дойти через слушатель, например, если пользователь закрыл это дело настройками конфиденциальности в телеграме — это тоже стоит учесть, например, в случае если мы хотим как-то обращаться к пользователю по нику или имени от лица бота.
Теперь мы принимаем какую-то информацию, когда пользователь пишет сообщение боту. Давайте с помощью этой информации построим примитивного эхо-бота — наш бот будет отвечать тем же текстом, что и написал нам пользователь. Для этого используем метод sendMessage, в который передадим id чата, в который мы будем отправлять сообщение, и содержание этого сообщения:
bot.on('text', async msg => < await bot.sendMessage(msg.chat.id, msg.text); >)
Вот так это дело работает:

Давайте немного усложним нашу конструкцию. Практически это смысла иметь не будет, но позволит нам разобрать ещё несколько методов для работы с ботом. Попробуем сначала выводить пользователю сообщение о том, что бот генерирует ответ на сообщение, а затем через 5 секунд бот будет удалять сообщение о генерации ответа и будет скидывать ответ, в нашем случае то же сообщение, что и скинул нам пользователь:
bot.on('text', async msg => < const msgWait = await bot.sendMessage(msg.chat.id, `Бот генерирует ответ. `); setTimeout(async () =>< await bot.deleteMessage(msgWait.chat.id, msgWait.message_id); await bot.sendMessage(msg.chat.id, msg.text); >, 5000); >)
В данном примере, мы скидываем пользователю сообщение о генерации и записываем ответ сервера на запрос отправки сообщения в переменную msgWait. Ответ сервера будет объектом того же вида, что и объект msg. Далее, через пять секунд, используем метод deleteMessage для удаления сообщения о генерации и скидываем сам ответ.


Попробуем немного изменить это дело. Вместо удаления сообщения и отправки нового сделаем отправку сообщения о генерации, а затем через 5 секунд отредактируем это сообщение на наш ответ:
bot.on('text', async msg => < const msgWait = await bot.sendMessage(msg.chat.id, `Бот генерирует ответ. `); setTimeout(async () =>< await bot.editMessageText(msg.text, < chat_id: msgWait.chat.id, message_id: msgWait.message_id >); >, 5000); >)
Для этого мы используем метод editMessageText, в который передаём строку на которую изменим наше сообщение, а также объект с данными о том, в каком чате и какое именно сообщение надо изменить.
Также хочу обратить Ваше внимание на то, что по-хорошему необходимо все наши методы обернуть в конструкцию try/catch, потому что возможен вариант, при котором наш код отлично отрабатывает, всё хорошо написано, однако, пользователь заблокировал бота, и если бот попытается ему написать сообщение, то сервер нам вернёт ошибку и весь бот может крашнуться.
bot.on('text', async msg => < try < await bot.sendMessage(msg.chat.id, msg.text); >catch(error) < console.log(error); >>)
Обрабатываем запуск бота
Далее поговорим про обработку запуска бота. Каждый раз, когда новый пользователь заходит в бота перед ним появляется кнопка «Запустить», вот так это выглядит:

Нажав эту кнопку пользователь отправит боту текстовую команду «/start». Сразу встаёт вопрос о том, как обработать это дело, чтобы поприветствовать нового пользователя и возможно записать какую-то информацию о пользователе в базу данных. Конечно, это можно сделать банальным способом и в слушателе текстового сообщения проверять, является ли сообщение пользователя текстовой командой «/start». Выглядеть это будет так:
bot.on('text', async msg => < try < if(msg.text == '/start') < await bot.sendMessage(msg.chat.id, `Вы запустили бота!`); >else < await bot.sendMessage(msg.chat.id, msg.text); >> catch(error) < console.log(error); >>)
Однако, у этого способа есть один минус, с которым лично я столкнулся. В ссылку на запуск бота мы можем передавать параметры и потом читать их. Это полезно, если мы, допустим, захотим сделать реферальную систему. Давайте я покажу, как это будет выглядеть, и в чем проблема. Для этого сделаем обработчик ещё одной команды, назовём её «/ref» и будем по этой команде отдавать пользователю уникальную ссылку на запуск бота:
bot.on('text', async msg => < try < if(msg.text == '/start') < await bot.sendMessage(msg.chat.id, `Вы запустили бота!`); >else if(msg.text == '/ref') < await bot.sendMessage(msg.chat.id, `$?start=$`); > else < await bot.sendMessage(msg.chat.id, msg.text); >> catch(error) < console.log(error); >>)
Обратите внимание, для того чтобы передать какую-то информацию при запуске бота, я передаю в ссылку на запуск бота get-параметр с id пользователя, который получил эту ссылку.
Теперь, когда если мы перейдем по ссылке, у нас будет та же самая кнопка «Запустить», которая отправит «/start» боту. Однако, пользователь видит, что он отправил команду «/start», но нам в боте возвращается уже немного другой объект, выглядеть он будет так:
< message_id: ID_СООБЩЕНИЯ, from: < id: ID_ПОЛЬЗОВАТЕЛЯ, is_bot: false, first_name: ИМЯ_ПОЛЬЗОВАТЕЛЯ, username: НИК_ПОЛЬЗОВАТЕЛЯ, language_code: 'ru' >, chat: < id: ID_ЧАТА, first_name: ИМЯ_ПОЛЬЗОВАТЕЛЯ, username: НИК_ПОЛЬЗОВАТЕЛЯ, type: 'private' >, date: ДАТА, text: '/start ID_ПОЛЬЗОВАТЕЛЯ_ИЗ_РЕФЕРАЛЬНОЙ_ССЫЛКИ', entities: [ < offset: 0, length: 6, type: 'bot_command' >] >
В текст сообщения нам возвращается уже не просто «/start», а вместе с ним то, что мы передали в get-параметр в ссылке на запуск. Следовательно, нам надо кое-что изменить в обработчике команды «/start».
bot.on('text', async msg => < try < if(msg.text.startsWith('/start')) < await bot.sendMessage(msg.chat.id, `Вы запустили бота!`); if(msg.text.length >6) < const refID = msg.text.slice(7); await bot.sendMessage(msg.chat.id, `Вы зашли по ссылке пользователя с ID $`); > > else if(msg.text == '/ref') < await bot.sendMessage(msg.chat.id, `$?start=$`); > else < await bot.sendMessage(msg.chat.id, msg.text); >> catch(error) < console.log(error); >>)
Как видите, сначала мы изменили проверку с равенства текста сообщения команде «/start», на проверку, начинается ли текст сообщения с команды «/start». Затем проверяем есть ли ещё какие-то параметры в команде запуска, проверяя длину сообщения (6 в данном случае это длина строки «/start»), а затем вырезаем из текста сообщения команду «/start» вместе с пробелом после неё методом slice и записываем то, что мы передаём в ссылке на запуск бота в переменную.
Сделать это можно и другим способом, используя слушатель onText и регулярные выражения:
bot.onText(/\/start/, async msg => < try < await bot.sendMessage(msg.chat.id, `Вы запустили бота!`); if(msg.text.length >6) < const refID = msg.text.slice(7); await bot.sendMessage(msg.chat.id, `Вы зашли по ссылке пользователя с ID $`); > > catch(error) < console.log(error); >>)
Слушатель принимает регулярное выражение, по которому будет проверять сообщение. Однако, если Вы будете применять данный способ, учтите, что если пользователь запустит бота, то сработает и слушатель onText с регулярным выражением, и слушатель on с типом ‘text’.
Меню команд для бота
Поговорим о том, как создать меню команду для бота. Выглядит меню команд следующим образом:

Создать это меню можно в BotFather или с помощью метода setMyCommands. Второй способ мне кажется удобнее и быстрее. В этот метод нам нужно передать массив объектов, в которых указаны сами команды и их описания в меню.
const commands = [ < command: "start", description: "Запуск бота" >, < command: "ref", description: "Получить реферальную ссылку" >, < command: "help", description: "Раздел помощи" >, ] bot.setMyCommands(commands);
Сначала мы задаём массив объектов с нашими командами, а затем передаём его в метод setMyCommands. Я создал команды, которые мы уже использовали до этого, а также создал новую команду help, давайте обработаем её в слушателе:
bot.on('text', async msg => < try < if(msg.text.startsWith('/start')) < await bot.sendMessage(msg.chat.id, `Вы запустили бота!`); if(msg.text.length >6) < const refID = msg.text.slice(7); await bot.sendMessage(msg.chat.id, `Вы зашли по ссылке пользователя с ID $`); > > else if(msg.text == '/ref') < await bot.sendMessage(msg.chat.id, `$?start=$`); > else if(msg.text == '/help') < await bot.sendMessage(msg.chat.id, `Раздел помощи`); >else < await bot.sendMessage(msg.chat.id, msg.text); >> catch(error) < console.log(error); >>)
Форматирование текста
Далее разберемся в форматировании текста в сообщениях. Разберем на примере обработки команды «/help». Для форматирования, стилизации, текста можно использовать либо HTML-верстку, либо Markdown-верстку. Для этого необходимо передавать в строку сообщения текст с тегами, а также передать объект с параметром parse_mode в метод sendMessage. Выглядеть это будет примерно так:
else if(msg.text == '/help') < await bot.sendMessage(msg.chat.id, `Раздел помощи HTML\n\nЖирный Текст\nТекст Курсивом\nТекст с Копированием\nПеречеркнутый текст\nПодчеркнутый текст\nкод на c++\nГиперссылка`, < parse_mode: "HTML" >); await bot.sendMessage(msg.chat.id, 'Раздел помощи Markdown\n\n*Жирный Текст*\n_Текст Курсивом_\n`Текст с Копированием`\n~Перечеркнутый текст~\n``` код ```\n||скрытый текст||\n[Гиперссылка](t.me)', < parse_mode: "MarkdownV2" >); >
Тогда команда «help» будет выводить нам следующее:
Вот список тегов с помощью которых Вы можете стилизовать текст в телеграм-ботах:
HTML:
- Текст — Жирный текст
- Текст — Текст курсивом
Текст— Текст, который можно скопировать нажатием на негоТекст— Перечеркнутый текст- Текст — Подчеркнутый текст
-
Текст
— Текст с оформлением кода
- Текст — Текст-гиперссылка
Markdown:
- *Текст* — Жирный текст
- _Текст_ — Текст курсивом
- `Текст` — Текст, который можно скопировать нажатием на него
- ~Текст~ — Перечеркнутый текст
- «` Текст «` — Текст с оформлением кода
- || Текст || — Скрытый текст
- [Текст](Ссылка) — Текст-гиперссылка
Стоит отметить, что все теги должны быть обязательно закрыты, иначе бот не отправит сообщение и вернёт ошибку. В этом плане, я советую делать стилизацию именно с помощью HTML-тегов, так как могут быть проблемы, если вы делаете админку или взаимодействие пользователей со стилизованным текстом. Например, если закинуть ссылку обычным текстом, а не гиперссылкой в сообщение, у которого parse_mode стоит на Markdown, то все нижние подчеркивания будут именно тегами, не отобразятся пользователю, и если их нечетное количество, то сообщение вообще не отправится. Также обратите внимание на то, что переход на следующую строку выполняется при помощи «\n» и в стилизации HTML, и в стилизации Markdown, здесь нельзя использовать тег
из HTML.
Также если мы хотим вставить эмодзи в наше сообщение, то можно просто скопировать эмодзи из телеграмма и вставить в нашу строку в коде, например:
await bot.sendMessage(msg.chat.id, `Вы запустили бота! `);

Также ещё подробнее поговорим про ссылки. Если мы укажем ссылку в сообщении, то сообщение придёт пользователю с превью ссылки. Добавлю для этого команду «/link» и её обработчик. Выглядит это так:
else if(msg.text == '/link') < await bot.sendMessage(msg.chat.id, `https://habr.com/`); >

Если мы хотим убрать превью в сообщении, то нам необходимо передать в метод sendMessage объект с параметром disable_web_page_preview со значением true:
else if(msg.text == '/link') < await bot.sendMessage(msg.chat.id, `https://habr.com/`, < disable_web_page_preview: true, >); >


Кстати, в этот же объект, помимо parse_mode и disable_web_page_preview, мы можем передать параметр disable_notification — это позволит отправить сообщение пользователю без уведомления:
else if(msg.text == '/link') < await bot.sendMessage(msg.chat.id, `https://habr.com/`, < disable_web_page_preview: true, disable_notification: true >); >
Меню-клавиатура

Далее обсудим меню-клавиатуру. Меню-клавиатуры делятся на два типа: то меню, которое находится рядом с вводом текста, и меню, которое привязано к сообщению. Между ними есть некоторая разница, о которой мы поговорим дальше. Давайте для начала попробуем создать меню, которое не привязано к сообщению — для удобства я буду его дальше называть просто клавиатура. Для этого создадим ещё одну команду «menu» и обработаем её:
else if(msg.text == '/menu') < await bot.sendMessage(msg.chat.id, `Меню бота`, < reply_markup: < keyboard: [ ['⭐️ Картинка', '⭐️ Видео'], ['⭐️ Аудио', '⭐️ Голосовое сообщение'] ] >>) >
В объект, в который мы раньше передавали параметры disable_web_page_preview, disable_notification, parse_mode теперь передаём reply_markup, который содержит массив массивов keyboard. Обратите внимание, что каждый массив в keyboard — это отдельная строка сверху вниз. То есть мы задаём массивами строки меню, а внутри строк задаём сами кнопки с помощью строк. Выглядеть то, что написано выше, в боте будет так:

Можем заметить, что кнопки получились какими-то большими. Для того, чтобы задать им адекватные размеры, можно в объект reply_markup передать параметр resize_keyboard со значением true. Давайте так и сделаем, и добавим в меню ещё несколько кнопок:
else if(msg.text == '/menu') < await bot.sendMessage(msg.chat.id, `Меню бота`, < reply_markup: < keyboard: [ ['⭐️ Картинка', '⭐️ Видео'], ['⭐️ Аудио', '⭐️ Голосовое сообщение'], ['⭐️ Контакт', '⭐️ Геолокация'], ['❌ Закрыть меню'] ], resize_keyboard: true >>) >

Далее встаёт вопрос о том, как обработать нажатие кнопки в нашей клавиатуре. Тут всё на самом деле просто. Когда пользователь нажимает на кнопку в меню, он скидывает боту текстовое сообщение с тем текстом, который написан на кнопке, который мы указывали в строках внутри массивов. Следовательно, будем обрабатывать нажатие на кнопку, как обычное текстовое сообщение:
else if(msg.text == '❌ Закрыть меню') < await bot.sendMessage(msg.chat.id, 'Меню закрыто', < reply_markup: < remove_keyboard: true >>) >
В данном примере, мы обработали нажатие кнопки закрытия меню. Обратите внимание на то, что если Вы просто отправите сообщение пользователю, клавиатура у него никуда не пропадёт. Для того, чтобы выключить клавиатуру, передаём параметр remove_keyboard со значением true в reply_markup. Как видим, клавиатура у нас пропала:

О том, как создать меню привязанное к сообщению поговорим чуть дальше.
Скидываем и обрабатываем изображение
Сейчас поговорим о том, как скинуть пользователю картинку, и как обработать сообщение с картинкой от пользователя.
Давайте будем скидывать картинку пользователю по нажатию кнопки «⭐️ Картинка» в меню. Саму кнопку мы уже создали, давайте обработаем её нажатие и с помощью метода sendPhoto, в который передадим ссылку на изображение, которое хотим скинуть пользователю:
else if(msg.text == '⭐️ Картинка')
В данном случае, мы скидываем пользователю картинку с помощью ссылки на изображение, однако, мы можем скинуть картинку и просто указав путь до картинки:
else if(msg.text == '⭐️ Картинка') < await bot.sendPhoto(msg.chat.id, './image.jpg'); >
Также можно скинуть картинку используя модуль fs:
else if(msg.text == '⭐️ Картинка') < //Скидываем изображение ссылкой await bot.sendPhoto(msg.chat.id, process.env.URL_TO_IMG); //Скидываем изображение указав путь await bot.sendPhoto(msg.chat.id, './image.jpg'); //Скидываем изображение с помощью Readable Stream const imageStream = fs.createReadStream('./image.jpg'); await bot.sendPhoto(msg.chat.id, imageStream); //Скидываем изображение с помощью буфера const imageBuffer = fs.readFileSync('./image.jpg'); await bot.sendPhoto(msg.chat.id, imageBuffer); >
Теперь наш бот будет вести себя примерно так:

Бот скидывает только картинку. Однако, нам может понадобится добавить какую-то подпись к этой картинке. Для этого передаем в метод sendPhoto объект с опциями, надпись можно передать в параметр caption, также можем задать parse_mode, как и в обычном текстовом сообщении:
const imageStream = fs.createReadStream('./image.jpg'); await bot.sendPhoto(msg.chat.id, imageStream, < caption: '⭐️ Картинка', parse_mode: 'HTML' >);
Далее разберемся с тем, как обработать сообщение с изображением от пользователя. Для этого используем слушатель с типом «photo»:
bot.on('photo', async img => < console.log(img); >)
Теперь, когда пользователь скинет сообщение с картинкой, телеграм вернёт нам объект следующего вида:
< message_id: 500, from: < id: ID_ПОЛЬЗОВАТЕЛЯ, is_bot: false, first_name: ИМЯ_ПОЛЬЗОВАТЕЛЯ, username: НИК_ПОЛЬЗОВАТЕЛЯ, language_code: 'ru' >, chat: < id: ID_ЧАТА, first_name: ИМЯ_ПОЛЬЗОВАТЕЛЯ, username: НИК_ПОЛЬЗОВАТЕЛЯ, type: 'private' >, date: ДАТА, photo: [ < file_id: ID_ФАЙЛА, file_unique_id: УНИКАЛЬНЫЙ_ID_ФАЙЛА, file_size: РАЗМЕР_ФАЙЛА, width: ШИРИНА_КАРТИНКИ, height: ВЫСОТА_КАРТИНКИ >, < file_id: ID_ФАЙЛА, file_unique_id: УНИКАЛЬНЫЙ_ID_ФАЙЛА, file_size: РАЗМЕР_ФАЙЛА, width: ШИРИНА_КАРТИНКИ, height: ВЫСОТА_КАРТИНКИ >, < file_id: ID_ФАЙЛА, file_unique_id: УНИКАЛЬНЫЙ_ID_ФАЙЛА, file_size: РАЗМЕР_ФАЙЛА, width: ШИРИНА_КАРТИНКИ, height: ВЫСОТА_КАРТИНКИ >, < file_id: ID_ФАЙЛА, file_unique_id: УНИКАЛЬНЫЙ_ID_ФАЙЛА, file_size: РАЗМЕР_ФАЙЛА, width: ШИРИНА_КАРТИНКИ, height: ВЫСОТА_КАРТИНКИ >] >
Мы похожее уже видели, когда обрабатывали текстовое сообщение. Только теперь мы имеем дело не со строкой text, а с массивом объектов photo. В этом массиве содержится наша картинка. Телеграм принял картинку, хранит у себя на сервере, и вернул нам несколько вариантов нашей картинки в разных размерах. Последний объект в этом массиве — это наш оригинал, а другие объекты содержат нашу картинку, только в сжатом виде. Но как же получить картинку, которую скидывал пользователь по информации содержащейся в объектах? Мы можем получить доступ к нашей картинке по file_id, который возвращает нам телеграм. Например, мы можем скачать файл по file_id с серверов телеграм используя метод downloadFile, в который передаём file_id, который нужно скачать, и директорию в которую будем скачивать файл. Выглядеть это будет так:
bot.on('photo', async img => < try < await bot.downloadFile(img.photo[img.photo.length-1].file_id, './image'); >catch(error) < console.log(error); >>)
Обратите внимание, что для того, чтобы выбрать наш оригинал в индексе массива я указал длину массива минус один — не будем забывать, что мы, программисты, считаем с нуля.
Давайте теперь сделаем следующее: когда пользователь скидывает нам изображение, будем скидывать ему в ответ несколько изображений объединенных в одно сообщение. Эти несколько изображений — это наш оригинал и сжатые его варианты. Для этого воспользуемся методом sendMediaGroup:
bot.on('photo', async img => < try < const photoGroup = []; for(let index = 0; index < img.photo.length; index++) < const photoPath = await bot.downloadFile(img.photo[index].file_id, './image'); photoGroup.push(< type: 'photo', media: photoPath, caption: `Размер файла: $байт\nШирина: $\nВысота: $` >) > await bot.sendMediaGroup(img.chat.id, photoGroup); for(let index = 0; index < photoGroup.length; index++) < fs.unlink(photoGroup[index].media, error => < if(error) < console.log(error); >>) > > catch(error) < console.log(error); >>)
Остановимся на этом поподробнее. В самом начале мы инициализируем массив. Он нам нужен для того, чтобы передать несколько картинок в метод sendMediaGroup и соответственно скинуть эти картинки пользователю. Метод sendMediaGroup принимает массив объектов, в которых мы указываем первым делом тип медиа-контента, в нашем случае это «photo». Также в объекте медиа-контента мы должны передать путь до нашего контента, этим путём могут служить, также как и в случае с методом sendPhoto: url, stream, buffer или путь до контента. Также мы можем указать file_id — его можно тоже указывать при отправке через sendPhoto или через sendMediaGroup, однако, в данном случае этот способ нам не подходит, так как какой бы file_id мы не указали, мы всегда будем скидывать оригинал картинки, а нас интересуют именно сжатые картинки. Дополнительно в объект медиа-контента мы можем передать caption — подпись под картинкой. В данном случае, я добавил в подпись под каждой картинкой её размер в байтах, ширину и высоту в пикселях — всё это нам возвращает телеграм. Теперь пройдясь циклом по всем вариантам нашей картинки, скачав все варианты и записав всю необходимую информацию, передаём наш массив объектов в метод sendMediaGroup, а затем удаляем все картинки, которые только что скачали. И теперь имеем следующее:


Скидываем и обрабатываем видео
Далее поговорим о том, как скинуть видео. Тут в общем-то всё аналогично фото, обработаем нажатие на кнопку на «⭐️ Видео» в нашем меню:
else if(msg.text == '⭐️ Видео') < await bot.sendVideo(msg.chat.id, './video.mp4'); >
Также мы туда можем добавить caption и parse_mode, и другие параметры, вроде disable_notification:
else if(msg.text == '⭐️ Видео') < await bot.sendVideo(msg.chat.id, './video.mp4', < caption: '⭐️ Видео', parse_mode: 'HTML' >); >

Теперь давайте обработаем сообщение пользователя с видео. Делаем это также с помощью слушателя с типом «video»:
bot.on("video", async video => < console.log(video); >)
Телеграм присылает нам следующее, когда пользователь отправляет сообщение с видео:
< message_id: ID_СООБЩЕНИЯ, from: < id: ID_СООБЩЕНИЯ, is_bot: false, first_name: ИМЯ_ПОЛЬЗОВАТЕЛЯ, username: НИК_ПОЛЬЗОВАТЕЛЯ, language_code: 'ru' >, chat: < id: ID_ЧАТА, first_name: ИМЯ_ПОЛЬЗОВАТЕЛЯ, username: НИК_ПОЛЬЗОВАТЕЛЯ, type: 'private' >, date: ДАТА, video: < duration: ДЛИТЕЛЬНОСТЬ_ВИДЕО, width: ВЫСОТА_ВИДЕО, height: ШИРИНА_ВИДЕО, file_name: ИМЯ_ФАЙЛА, mime_type: ТИП_ФАЙЛА, thumbnail: < file_id: ID_ИЗОБРАЖЕНИЯ, file_unique_id: УНИКАЛЬНОЕ_ID_ИЗОБРАЖЕНИЯ, file_size: РАЗМЕР_ИЗОБРАЖЕНИЯ, width: ШИРИНА_ИЗОБРАЖЕНИЯ, height: ВЫСОТА_ИЗОБРАЖЕНИЯ >, thumb: < file_id: ID_ИЗОБРАЖЕНИЯ, file_unique_id: УНИКАЛЬНОЕ_ID_ИЗОБРАЖЕНИЯ, file_size: РАЗМЕР_ИЗОБРАЖЕНИЯ, width: ШИРИНА_ИЗОБРАЖЕНИЯ, height: ВЫСОТА_ИЗОБРАЖЕНИЯ >, file_id: ID_ВИДЕО, file_unique_id: УНИКАЛЬНОЕ_ID_ВИДЕО, file_size: РАЗМЕР_ВИДЕО > >
Как видим, телеграм предоставляет нам информацию о видео и изображениях. Изображения — это миниатюры видео. Давайте сделаем следующее: когда пользователь скидывает видео, бот будет в ответ кидать само видео, миниатюру и информацию о видео. Выглядеть это будет следующим образом:
bot.on("video", async video => < try < const thumbPath = await bot.downloadFile(video.video.thumbnail.file_id, './image'); await bot.sendMediaGroup(video.chat.id, [ < type: 'video', media: video.video.file_id, caption: `Название файла: $\nВес файла: $ байт\nДлительность видео: $ секунд\nШирина кадра в видео: $\nВысота кадра в видео: $` >, < type: 'photo', media: thumbPath, >]); fs.unlink(thumbPath, error => < if(error) < console.log(error); >>) > catch(error) < console.log(error); >>)

Обратите внимание, что видео-файл я скидываю по его file_id, а миниатюру скачиваю, скидываю и затем удаляю, потому что телеграм не даёт скинуть миниатюру методом sendPhoto по file_id. Также важно, что в этот раз я добавил в методе sendMediaGroup параметр caption лишь к одному медиа-файлу и надпись отобразилась в сообщении.
Также есть важный момент, который нужно учесть. Иногда, когда пользователь скидывает видео, допустим в том же .mp4, телеграм может это сжать и скинуть в .gif.
Скидываем и обрабатываем аудио
Ситуация уже знакомая нам, скидываем аудио также, как видео и фото, обрабатывая кнопку «⭐️ Аудио»:
else if(msg.text == '⭐️ Аудио') < await bot.sendAudio(msg.chat.id, './audio.mp3', < caption: '⭐️ Аудио', parse_mode: 'HTML' >); >
Обработка аудио выполняется тоже похожим образом, только используем слушатель с типом «audio»:
bot.on('audio', async audio => < try < await bot.sendAudio(audio.chat.id, audio.audio.file_id, < caption: `Название файла: $\nВес файла: $ байт\nДлительность аудио: $ секунд` >) > catch(error) < console.log(error); >>)

Телеграм-сервер возвращает похожий на предыдущие примеры объект:
< message_id: 653, from: < id: 764548588, is_bot: false, first_name: 'shavrin', username: 'zloishavrin', language_code: 'ru' >, chat: < id: 764548588, first_name: 'shavrin', username: 'zloishavrin', type: 'private' >, date: 1686339341, audio: < duration: 1, file_name: 'audio.mp3', mime_type: 'audio/mpeg', file_id: 'CQACAgIAAxkBAAICjWSDfw0AAZdXcrZjG-2n840P-NqNIQACOzEAAi3wIUh2fGtPn59fBi8E', file_unique_id: 'AgADOzEAAi3wIUg', file_size: 19776 >>
Хочу обратить внимание на то, что если пользователь скинет несколько видео, фото или аудио одним сообщением, то они не вернутся Вам одним сообщением с массивом audio или video-объектов, просто сработают несколько слушателей и эти файлы будут считаться отдельными сообщениями и обрабатываться будут также отдельно.
С голосовыми ситуация аналогичная, ничего интересного:
else if(msg.text == '⭐️ Голосовое сообщение') < await bot.sendVoice(msg.chat.id, './audio.mp3', < caption: '⭐️ Голосовое сообщение', parse_mode: 'HTML' >); >
И также обрабатываем голосовые сообщения:
bot.on('voice', async voice => < try < await bot.sendAudio(voice.chat.id, voice.voice.file_id, < caption: `Вес файла: $байт\nДлительность аудио: $ секунд` >) > catch(error) < console.log(error); >>)

Скидываем, запрашиваем и обрабатываем контакт
Дальше разберемся с тем, как скинуть пользователю контакт. Для этого обработаем кнопку «⭐️ Контакт», используя метод sendContact, в который передадим строку с номером телефона и именем контакта:
else if(msg.text == '⭐️ Контакт') < //Скидываем контакт await bot.sendContact(msg.chat.id, process.env.CONTACT, `Контакт`, < reply_to_message_id: msg.message_id >); >
Обратите внимание, что в телеграме стоит проверка на формат номера, и если мы укажем не номер, то телеграм вернёт нам ошибку.
Также я добавил параметр reply_to_message_id — в этот параметр мы можем передать message_id сообщения, на которое мы хотим ответить сообщением, в которое передаем message_id.

Теперь давайте немного изменим нашу кнопку. И вместо того, чтобы просто скидывать контакт, мы сначала будем его запрашивать, а затем обрабатывать. Для этого изменим обработку команды «/menu», в которой мы присылаем пользователю нашу клавиатуру:
else if(msg.text == '/menu') < await bot.sendMessage(msg.chat.id, `Меню бота`, < reply_markup: < keyboard: [ ['⭐️ Картинка', '⭐️ Видео'], ['⭐️ Аудио', '⭐️ Голосовое сообщение'], [, '⭐️ Геолокация'], ['❌ Закрыть меню'] ], resize_keyboard: true > >) >
Как видите, теперь передаём вместо просто строки ‘⭐️ Контакт’ объект с параметрами text и request_contact, тем самым запрашивая контакт пользователя при нажатии на кнопку:

Теперь обработаем контакт. Для этого добавляем слушатель с типом «contact». Этот слушатель вернёт нам следующий объект, если пользователь скинул контакт:
< message_id: ID_СООБЩЕНИЯ, from: < id: ID_ПОЛЬЗОВАТЕЛЯ, is_bot: false, first_name: ИМЯ_ПОЛЬЗОВАТЕЛЯ, username: НИК_ПОЛЬЗОВАТЕЛЯ, language_code: 'ru' >, chat: < id: ID_ЧАТА, first_name: ИМЯ_ПОЛЬЗОВАТЕЛЯ, username: НИК_ПОЛЬЗОВАТЕЛЯ, type: 'private' >, date: ДАТА, reply_to_message: < message_id: ID_СООБЩЕНИЯ_В_КОТОРОМ_СКИДЫВАЛИ_КНОПКУ, from: < id: ID_БОТА, is_bot: true, first_name: ИМЯ_БОТА, username: НИК_БОТА >, chat: < id: ID_ЧАТА, first_name: ИМЯ_ПОЛЬЗОВАТЕЛЯ, username: НИК_ПОЛЬЗОВАТЕЛЯ, type: 'private' >, date: ДАТА_СООБЩЕНИЯ_В_КОТОРОМ_СКИДЫВАЛИ_КНОПКУ, text: ТЕКСТ_СООБЩЕНИЯ_В_КОТОРОМ_СКИДЫВАЛИ_КНОПКУ >, contact: < phone_number: НОМЕР_КОНТАКТА, first_name: ИМЯ_КОНТАКТА, user_id: ID_ПОЛЬЗОВАТЕЛЯ >>
Давайте обработаем это следующим образом: когда боту приходит контакт, бот скидывает номер и имя контакта, отвечая на сообщение с контактом:
bot.on('contact', async contact => < try < await bot.sendMessage(contact.chat.id, `Номер контакта: $\nИмя контакта: $`); > catch(error) < console.log(error); >>)
Выглядеть это будет так:

Скидываем, запрашиваем и обрабатываем геолокацию
Обработаем нажатие кнопки «⭐️ Геолокация» и будем скидывать пользователю геолокацию Красной Площади, указав широту и долготу нужной нам координаты:
else if(msg.text == '⭐️ Геолокация') < const latitudeOfRedSquare = 55.753700; const longitudeOfReadSquare = 37.621250; await bot.sendLocation(msg.chat.id, latitudeOfRedSquare, longitudeOfReadSquare, < reply_to_message_id: msg.message_id >) >

Теперь провернём тоже самое, что мы делали с контактами, только для геолокации — сделаем запрос геолокации по кнопке и будем возвращать в ответ на сообщение с геолокацией координаты геолокации (широту и долготу). Начнём с того, что изменим кнопку «⭐️ Геолокация»:
else if(msg.text == '/menu') < await bot.sendMessage(msg.chat.id, `Меню бота`, < reply_markup: < keyboard: [ ['⭐️ Картинка', '⭐️ Видео'], ['⭐️ Аудио', '⭐️ Голосовое сообщение'], [, ], ['❌ Закрыть меню'] ], resize_keyboard: true > >) >

Далее обработаем сообщение пользователя с геолокацией. Сообщение с геолокацией возвращает следующий объект:
< message_id: ID_СООБЩЕНИЯ, from: < id: ID_ПОЛЬЗОВАТЕЛЯ, is_bot: false, first_name: ИМЯ_ПОЛЬЗОВАТЕЛЯ, username: НИК_ПОЛЬЗОВАТЕЛЯ, language_code: 'ru' >, chat: < id: ID_ЧАТА, first_name: ИМЯ_ПОЛЬЗОВАТЕЛЯ, username: НИК_ПОЛЬЗОВАТЕЛЯ, type: 'private' >, date: ДАТА, reply_to_message: < message_id: ID_СООБЩЕНИЯ_В_КОТОРОМ_СКИДЫВАЛИ_КНОПКУ, from: < id: ID_БОТА, is_bot: true, first_name: ИМЯ_БОТА, username: НИК_БОТА >, chat: < id: ID_ЧАТА, first_name: ИМЯ_ПОЛЬЗОВАТЕЛЯ, username: НИК_ПОЛЬЗОВАТЕЛЯ, type: 'private' >, date: ДАТА_СООБЩЕНИЯ_В_КОТОРОМ_СКИДЫВАЛИ_КНОПКУ, text: ТЕКСТ_СООБЩЕНИЯ_В_КОТОРОМ_СКИДЫВАЛИ_КНОПКУ >, location: < latitude: ШИРОТА_ГЕОЛОКАЦИИ, longitude: ДОЛГОТА_ГЕОЛОКАЦИИ >>
Сделаем следующий слушатель:
bot.on('location', async location => < try < await bot.sendMessage(location.chat.id, `Широта: $\nДолгота: $`); > catch(error) < console.log(error); >>)

Делаем ещё одно меню
Выше я уже говорил, что меню можно создать нескольких видов. Выше мы уже создали меню-клавиатуру и меню команд. Теперь создадим меню, как я писал выше, привязанное к сообщению. Далее будем называть это инлайн-клавиатура. Для этого создадим новую команду и обработаем её:
else if(msg.text == '/second_menu') < await bot.sendMessage(msg.chat.id, `Второе меню`, < reply_markup: < inline_keyboard: [ [, ], [], [], [] ] > >) >
Как видим мы также, как с обычной клавиатурой, создаём массив массивов в объекте reply_markup. Каждый массив в inline_keyboard — это новая строка в меню. В отличие от обычной клавиатуры, мы передаём в массив не строки, а объекты с текстом и callback_data. Обрабатывать мы будем нажатия кнопок не обычным текстовым сообщением, а уже коллбеками. Вот так будет выглядеть созданная нами инлайн-клавиатура:

Теперь давайте попробуем обработать кнопку «Закрыть Меню». Для этого будем использовать слушатель с типом «callback_query»:
bot.on('callback_query', async ctx => < try < console.log(ctx); >catch(error) < console.log(error); >>)
Коллбеки возвращают нам с серверов телеграм следующий объект:
< id: ID_КОЛЛБЕКА, from: < id: ID_ПОЛЬЗОВАТЕЛЯ, is_bot: false, first_name: ИМЯ_ПОЛЬЗОВАТЕЛЯ, username: НИК_ПОЛЬЗОВАТЕЛЯ, language_code: 'ru' >, message: < message_id: ID_СООБЩЕНИЯ, from: < id: ID_БОТА, is_bot: true, first_name: ИМЯ_БОТА, username: НИК_БОТА >, chat: < id: ID_ЧАТА, first_name: ИМЯ_ПОЛЬЗОВАТЕЛЯ, username: НИК_ПОЛЬЗОВАТЕЛЯ, type: 'private' >, date: ДАТА, text: ТЕКСТ_СООБЩЕНИЯ, reply_markup: < inline_keyboard: [Array] >>, chat_instance: ЗАВИСИМЫЙ_ЧАТ, data: КОЛЛБЕК_ДАТА >
Как видим, в объекте возвращается информация о сообщении, к которому привязана кнопка, на которую нажали, информация о пользователе, который нажал кнопку, и самое важное — callback_data, которую мы указывали в кнопке. С помощью вот этого параметра callback_data будем обрабатывать нажатие на кнопку:
bot.on('callback_query', async ctx => < try < switch(ctx.data) < case "closeMenu": await bot.deleteMessage(ctx.message.chat.id, ctx.message.message_id); break; >> catch(error) < console.log(error); >>)
В данном случае, мы удаляем сообщение с нашей инлайн-клавиатурой по нажатию кнопки «Закрыть Меню». Дальше в этом же слушателе будем обрабатывать нажатия других кнопок по параметру callback_data.
Теперь давайте немного модифицируем всё это дело. И наше меню будем скидывать не просто сообщением по команде, а в ответ на сообщение-команду:
else if(msg.text == '/second_menu') < await bot.sendMessage(msg.chat.id, `Второе меню`, < reply_markup: < inline_keyboard: [ [, ], [], [], [] ] >, reply_to_message_id: msg.message_id >) >
Теперь мы можем сделать следующее: в обработчике закрытия меню удалять не только сообщение с самим меню, а ещё и удалять сообщение-команду, по которому было вызвано меню:
case "closeMenu": await bot.deleteMessage(ctx.message.chat.id, ctx.message.message_id); await bot.deleteMessage(ctx.message.reply_to_message.chat.id, ctx.message.reply_to_message.message_id); break;
Мы можем такое сделать, потому что тогда наш коллбек будет возвращать следующий объект:
< id: ID_КОЛЛБЕКА, from: < id: ID_ПОЛЬЗОВАТЕЛЯ, is_bot: false, first_name: ИМЯ_ПОЛЬЗОВАТЕЛЯ, username: НИК_ПОЛЬЗОВАТЕЛЯ, language_code: 'ru' >, message: < message_id: ID_СООБЩЕНИЯ, from: < id: ID_БОТА, is_bot: true, first_name: ИМЯ_БОТА, username: НИК_БОТА >, chat: < id: ID_ЧАТА, first_name: ИМЯ_ПОЛЬЗОВАТЕЛЯ, username: НИК_ПОЛЬЗОВАТЕЛЯ, type: 'private' >, date: ДАТА, reply_to_message: < message_id: ID_СООБЩЕНИЯ_2, from: [Object], chat: [Object], date: ДАТА, text: КОМАНДА_ВЫЗОВА_МЕНЮ, entities: [Array] >, text: ТЕКСТ_СООБЩЕНИЯ, reply_markup: < inline_keyboard: [Array] >>, chat_instance: ЗАВИСИМЫЙ_ЧАТ, data: КОЛЛБЕК_ДАТА >
Скидываем и обрабатываем стикеры
Далее обработаем нажатие кнопки «Стикер» на нашей инлайн-клавиатуре. Для этого будем использовать метод sendSticker:
case "sticker": await bot.sendSticker(ctx.message.chat.id, `./image.jpg`); break;
Хочу обратить ваше внимание на то, что мы можем скинуть любое изображение стикером, аналогично тому, как мы это делали с методом sendPhoto.
Теперь обработаем сообщение пользователя со стикером с помощью слушателя с типом «sticker», который будет возвращать нам следующий объект:
< message_id: ID_СООБЩЕНИЯ, from: < id: ID_ПОЛЬЗОВАТЕЛЯ, is_bot: false, first_name: ИМЯ_ПОЛЬЗОВАТЕЛЯ, username: НИК_ПОЛЬЗОВАТЕЛЯ, language_code: 'ru' >, chat: < id: ID_ЧАТА, first_name: ИМЯ_ПОЛЬЗОВАТЕЛЯ, username: НИК_ПОЛЬЗОВАТЕЛЯ, type: 'private' >, date: ДАТА, sticker: < width: ШИРИНА_СТИКЕРА, height: ДЛИНА_СТИКЕРА, emoji: '', //ЭМОДЗИ К КОТОРОМУ ПРИВЯЗАН СТИКЕР set_name: ИМЯ_СТИКЕРА, is_animated: false, //БУЛЕВА ПЕРЕМЕННАЯ, КОТОРАЯ ОТОБРАЖАЕТ АНИМИРОВАН СТИКЕР ИЛИ НЕТ is_video: false, //БУЛЕВА ПЕРЕМЕННАЯ, КОТОРАЯ ОТОБРАЖАЕТ СТИКЕР ВИДЕОФОРМАТА ИЛИ НЕТ type: 'regular', thumbnail: < file_id: ID_МИНИАТЮРЫ, file_unique_id: УНИКАЛЬНЫЙ_ID_МИНИАТЮРЫ, file_size: РАЗМЕР_МИНИАТЮРЫ, width: ШИРИНА_МИНИАТЮРЫ, height: ДЛИНА_МИНИАТЮРЫ >, thumb: < file_id: ID_МИНИАТЮРЫ, file_unique_id: УНИКАЛЬНЫЙ_ID_МИНИАТЮРЫ, file_size: РАЗМЕР_МИНИАТЮРЫ, width: ШИРИНА_МИНИАТЮРЫ, height: ДЛИНА_МИНИАТЮРЫ >, file_id: ID_ФАЙЛА_СТИКЕРА, file_unique_id: УНИКАЛЬНЫЙ_ID_ФАЙЛА_СТИКЕРА, file_size: РАЗМЕР_ФАЙЛА_СТИКЕРА > >
Давайте теперь сделаем следующее: когда пользователь скидывает стикер, будем проверять, какого вида стикер используя информацию из объекта (булевы переменные) и будем скидывать стикер пользователю картинкой, видео или анимацией:
bot.on('sticker', async sticker => < try < const stickerPath = await bot.downloadFile(sticker.sticker.file_id, './image'); if(sticker.sticker.is_video) < await bot.sendVideo(sticker.chat.id, stickerPath); >else if(sticker.sticker.is_animated) < await bot.sendAnimation(sticker.chat.id, stickerPath); >else < await bot.sendPhoto(sticker.chat.id, stickerPath); >fs.unlink(stickerPath, error => < if(error) < console.log(error); >>) > catch(error) < console.log(error); >>)

Обратите внимание, что я сделал некрасиво: в любом случае, будь стикер анимацей, видео или картинкой скачиваю файл в папку с названием «image» — так лучше, конечно, не делать. Также я не отправляю файлы по file_id, а скачиваю и затем их отправляю, потому что если попробовать скинуть стикер-файл по file_id используя методы sendPhoto или sendVideo, телеграм вернёт ошибку о том, что нельзя скинуть файл типа «стикер», как «фото» или «видео», однако, метод sendAnimation позволяет отправлять стикеры анимацией по их file_id, тогда это будет выглядеть следующим образом:
else if(sticker.sticker.is_animated)
Круглое видео
Обработаем кнопку «Круглое Видео», по нажатию на которую будем скидывать пользователю видео круглого формата. Сделать это можно с помощью метода sendVideoNote:
case "circleVideo": await bot.sendVideoNote(ctx.message.chat.id, './video.mp4'); break;
В общем-то с круглыми видео ничего интересного нету — всё аналогично обычному видео. Кстати, в объект опций мы можем передать параметр protect_content со значением true и пользователь не сможет пересылать сообщение:
case "circleVideo": await bot.sendVideoNote(ctx.message.chat.id, './video.mp4', < protect_content: true >); break;
Проверка подписки на канал
Далее разберемся с тем, как сделать, чтобы бот проверял подписку на канал. Для этого необходимо поставить бота админом канала, на который он будет проверять подписку и использовать метод getChatMember:
case "checkSubs": const subscribe = await bot.getChatMember(process.env.ID_CHAT, ctx.from.id); console.log(subscribe); break;
В этот метод передаём ID нашего канала или чата — получить этот ID можно, например, скинув ссылку на канал или чат в специального бота, который возвращает ID, он будет в формате «-100XXXXXXXXXX», и также передаём в этот метод ID пользователя, подписку которого будем проверять. Данный метод вернёт объект такого вида:
< user: < id: ID_ПОЛЬЗОВАТЕЛЯ, is_bot: false, first_name: ИМЯ_ПОЛЬЗОВАТЕЛЯ, username: НИК_ПОЛЬЗОВАТЕЛЯ, language_code: 'ru' >, status: СТАТУС_ПОЛЬЗОВАТЕЛЯ, is_anonymous: false >
Будем обращаться к статусу пользователя. Статусы бывают следующие:
- left — пользователь не подписан
- kicked — пользователь заблокирован
- member — пользователь подписан
- administrator — пользователь является администратором
- creator — пользователь является создателем
Обработаем статус пользователя и в зависимости от него будем выводить пользователю сообщение:
case "checkSubs": const subscribe = await bot.getChatMember(process.env.ID_CHAT, ctx.from.id); if(subscribe.status == 'left' || subscribe.status == 'kicked') < await bot.sendMessage(ctx.message.chat.id, `Вы не являетесь подписчиком!`, < parse_mode: 'HTML' >) > else < await bot.sendMessage(ctx.message.chat.id, 'Вы являетесь подписчиком!', < parse_mode: 'HTML' >) > break;
Подключаем оплату
Для того, чтобы подключить оплату к своему бота для начала стоит определиться с платежной системой через которую будут совершаться платежи. У телеграма есть хороший выбор стандартных платежный систем, однако, никто не запрещает Вам подключить стороннюю платежную систему, но сейчас разберемся именно со стандартными телеграмовскими платежными системами. Для их подключения необходимо перейти в BotFather, выбрать своего бота, перейти в раздел Payments, а дальше в выбранной Вами платежной системе получить provider token — ключ, по которому мы будем подключать оплату.
Давайте отправим счёт на оплату пользователю по нажатию кнопки «Купить Файл». Для этого будем использовать метод sendInvoice:
case "buyFile": await bot.sendInvoice(ctx.message.chat.id, 'Купить Файл', 'Покупка файла', 'file', process.env.PROVIDER_TOKEN, 'RUB', [< label: 'Файл', amount: 20000 >]); break;
В этот метод мы передаём название платежа, описание платежа, payload — это информация, которая передаётся в платеж, по ней мы будем отслеживать нужный платеж, а у пользователя она нигде не отобразится, провайдер-токен, валюта (в разных платежках поддерживаются разные валюты) и также массив объектов с товарами, которые будет оплачивать пользователь. Обратите внимание, что валютой я указал рубли (код валюты в ISO 4217), а цену на товар указал в копейках. Также можно в метод передать другие параметры, можно добавить миниатюру платежа, включить необходимость ввода некоторых данных для пользователя, а также данные для фискализации платежа.
Далее по кнопке «Купить Файл» пользователю отправится следующее сообщение:

Далее пользователь нажмёт кнопку оплаты, введёт свои данные, и когда подтвердит введённые данные, бот должен отправить окончательное подтверждение перед оформлением заказа. Сделать это можно при помощи обработки такого слушателя:
bot.on('pre_checkout_query', async ctx => < try < await bot.answerPreCheckoutQuery(ctx.id, true); >catch(error) < console.log(error); >>)
Когда мы окончательно подтвердили оформление заказа, пользователь может совершить платеж и мы должны его как-то обработать. Для этого будем использовать слушатель с типом «successful_payment», который возвращает объект следующего вида:
< message_id: ID_СООБЩЕНИЯ, from: < id: ID_ПОЛЬЗОВАТЕЛЯ, is_bot: false, first_name: ИМЯ_ПОЛЬЗОВАТЕЛЯ, username: НИК_ПОЛЬЗОВАТЕЛЯ, language_code: 'ru' >, chat: < id: ID_ЧАТА, first_name: ИМЯ_ПОЛЬЗОВАТЕЛЯ, username: НИК_ПОЛЬЗОВАТЕЛЯ, type: 'private' >, date: ДАТА, successful_payment: < currency: КОД_ВАЛЮТЫ, total_amount: СУММА_ПЛАТЕЖА, invoice_payload: PAYLOAD, telegram_payment_charge_id: ID_ПЛАТЕЖА_ТЕЛЕГРАМ, provider_payment_charge_id: ID_ПЛАТЕЖА_ПЛАТЕЖНАЯ_СИСТЕМА >>
Исходя из этой информации мы можем обработать совершённый платеж, например:
bot.on('successful_payment', async ctx => < try < await bot.sendDocument(ctx.chat.id, `./$.txt`, < caption: `Спасибо за оплату $!` >) > catch(error) < console.log(error); >>)
Используем метод sendDocument для того, чтобы скинуть пользователю файл, который мы находим благодаря информации из payload, и в подписи к нему выводим информацию из payload, которую мы изначально передали в методе sendInvoice. Пользователь увидит следующее:

Заключение
В этом материале постарался максимально просто и с примерами рассказать об основных возможностях телеграм-ботов и как ими управлять с помощью NodeJS. Тут в принципе есть ещё о чем рассказать: веб-хуки для телеграм ботов, управление чатами, каналами, игры и веб-приложения в телеграм-ботах. Если кому-то будет интересна эта тема, то продолжу об этом писать.
Также выложил код всего, что было написано в этой статье на GitHub с комментариями, посмотреть и скачать можно здесь.
- телеграм бот
- телеграм бот javascript
- телеграм бот nodejs
- телеграм боты
- telegram
- telegrambot
- telegram bot
- telegram bots
- javascript
- nodejs

