Делаем телеграм бота на JS
Создано специально для телеграм канала https://t.me/debug_u
Last updated
Создано специально для телеграм канала https://t.me/debug_u
Last updated
В данной статье я хочу поделиться опытом создания телеграм ботов. Сперва я покажу как можно настроить свое рабочее окружение, после чего преступим непосредственно к написанию самого бота.
Пожалуйста, не воспринимайте меня как гуру JavaScript или библиотеки, которую мы будем использовать в процессе разработки. Я лишь хочу поделиться теми знаниями, которые сумел приобрести сам, и рассказать про подводные камни, с которыми столкнулся.
Те, кто знаком с NodeJS, можете пропустить или очень бегло просмотреть раздел "подготовки", где объясняется как создать приложение на ноде.
Также обращаю внимание, что я сижу на MacOS и использую команды для нее. Если у вас другая ОС либо же терминальные команды отличаются, попробуйте погуглить!
Больше информации вы можете узнать на канале Debug_Yourself.
Связаться с автором можно https://t.me/arutemu_su
Как и было упомянуто выше, в качестве языка программирования мы выберем JS. Стоит ли знать его для того чтобы понять эту статью? Наверное да. Если вы не знаете JS хотя бы на уровне синтаксиса, многое будет вам непонятно, хотя я постараюсь осветить каждую строку кода по возможности. В любом случае никто вам не запрещает потратить пару часов/дней на то, чтобы познакомиться с JS и потом вернуться к этой статье.
Итак. Чтобы мы могли исполнять JS код на нашей машине, нам потребуется NodeJS. Многие читатели наверняка слышали/знают о том, что это такое. Если коротко: NodeJS (нода) – это платформа, которая позволяет исполнять JS код на компьютере. Скачайте ее в зависимости от вашей ОС. Если после установки ввести команду в терминале
вы должны получить версию по типу v14.2.0
.
Теперь мы можем непосредственно создать проект на ноде. Для этого создайте папку на рабочем столе (или где вам удобнее) с названием проекта. Я назвал папку debug_u_bot
.
Также нам потребуется редактор для написания кода. Какой выбирать – дело каждого. Я пользуюсь Visual Studio Code. Довольно хороший инструмент, можете попробовать.
Открываем редактор, а в нем и нашу созданную папку. Разумеется, сейчас там пусто.
Вместе с нодой идет такой инструмент как npm. Сам по себе npm – это менеджер пакетов, с помощью которого вы можете устанавливать в свой проект различные сторонние зависимости. Для начала давайте при помощи него инициализируем проект. Откройте терминал в вашем редакторе (в VSC он находится в верхнем меню), либо же можете открыть терминал отдельно (только вам потребуется перейти непосредственно в вашу папку). Введите там команду
Этой командой вы инициализируете ваш проект, и в папке появится файл package.json. Часть команды --yes
– это параметр, который автоматически пропускает вопросы по типу версии проекта, автора и т.п. Если запустить без него, то сможете ввести исходные данные, но в нашем случае в этом нет необходимости.
Итак. Если посмотреть на файл package.json, мы увидим следующее:
Сам по себе этот файл определяет настройки проекта, скрипты и зависимости, которые этот проект использует. Позже мы вернемся к нему.
Теперь давайте создадим файл app.js в корне нашего каталога. Здесь мы будем писать наш будущий код. Однако прежде чем приступить к этому, нам необходимо установить несколько зависимостей. В терминале пишем команду
Этой командой мы говорим npm, чтобы он установил пакет express. В данном случае i
– сокращение от install. Вы также можете написать npm install express
и получить аналогичный результат.
Что же такое express? Это минималистичный фреймворк для написаний NodeJS приложений. Он нам потребуется для быстрого создания сервера. Кому интересно, можете почитать про него тут.
Итак. Открываем наконец-таки файл app.js и пишем следующее
Коротко пробежимся что к чему: на первой строке мы добавляем модуль 'express' и кладем его в переменную express
На 3 строке мы инициализируем наше приложение и сохраняем в переменной app
. На 4 строке создаем переменную с портом, на котором будет заводиться наш сервер. На 6 строке создается обработчик для корневого запроса '/'
, но об этом чуть ниже. На 10 строке мы запускаем наш HTTP сервер на заданном ранее порту PORT
.
Введите в терминале команду node app
или node app.js
и, если все сделано правильно, у вас запуститься локальный сервер. Также в терминале вы увидите следующее
Если в браузере перейти по адресу http://lcalhost:3000/ , то вы увидите
Пока что мы все еще не пишем бота, однако уверенно готовимся к этому. Еще немного поработаем с нодой, так как именно она поможет запускать нашего дальнейшего бота.
В файле app.js попробуйте заменить текст, который выводится при обработке get запроса
Теперь по идее если опять зайти на http://localhost:3000/, то текст должен поменяться, но этого не происходит. Причина кроется в том, что для того, чтобы наши изменения заимели силу, необходимо вручную перезапустить сервер. Сначала остановите его (ctrl+c), а потом снова запустите командой node app
. Теперь изменения вступили в силу, однако каждый раз вручную перезагружать сервер утомляет.
Чтобы сервер сам автоматически перезагружался после каждого нашего изменения, давайте установим еще один пакет через терминал npm i nodemon -g
. Это установит данный пакет глобально. ВАЖНО! У меня на MacOS для глобальной установки необходимо ставить через sudo, то есть sudo npm i nodemon -g
.
Теперь вы можете запустить проект в терминале командой nodemon app
. Вновь измените текст на что-нибудь и сохраните файле app.js. Сервер рестартанет сам, и вы увидите изменения.
Последнее, что хотелось бы сделать, это внести пару изменений. Для импорта и экспорта файлов я предпочел бы использовать более современный синтаксис import/from
. Давайте изменим 1 строку файла app.js следующим образом:
Чтобы код работал, нам также необходимо кое-что добавить в файле package.json
а именно надо добавить еще одно поле type со значением module. Можете в конец через запятую вставить "type": "module"
или просто скопировать мой код. Также давайте пропишем скрипт для более лаконичного запуска нашего приложения. На 6 строке "scripts"
удалить test и замените его на "start": "nodemon app.js"
. Этим мы по сути команду nodemon app.js просто пакуем в команду start. Вот как должен выглядеть файл package.json теперь
Теперь чтобы запустить проект, введите в терминал команду npm run start
.
В качестве примера мы напишем бота, в котором можно хранить задачи. Получиться своего рода taskManagerBot с минимальным функционалом. При это в процессе его создания мы сможем рассмотреть много базовых тем и приемов.
Всех ботов в тг надо создавать через отца всея ботов - @BotFather. Командой /newbot
приступаем к созданию, введя имя (я ввел debug_u_lesson). После надо ввести username вашего бота. Разумеется, он должен быть уникальным для телеграма и заканчиваться на bot
. Я ввел debug_u_lesson_bot, вы придумайте что-то свое.
Введя все этого, мы получаем наш заветный token, ради которого все и затевалось. Желательно его никому не показывать, ведь иначе кто-то другой сможет управлять вашим ботом. Хотя вы всегда можете поменять токен в BotFather. Там же вы можете немного кастомизировать бота, если напишите /mybots
. Там можно изменять имя/описание/пикчу или подключать платежи для сбера/яндекса. Попробуйте изменить описание на что-то подобное "Приветствую. Я учусь создавать ботов вместе с @debug_u".
Несмотря на то, что проект предвещает быть простым и незамысловатым, предлагаю не мешать все в один файл app.js. Давайте такие вещи как порт сервера и токен телеграма мы будем хранить в отдельном файле. Сразу скажу, что в идеале их надо хранить в переменных окружения, но в нашем случае не хочу пока этим грузить. Если хотите почитать про то, что это значит, то вот.
В общем, создаем файл config.js в корне нашего проекта и пишем туда следующее:
Здесь мы просто объявляем две константы и командой export
позволяем использовать их в других файлах/модулях.
Наш файл app.js немного видоизменится:
Попробуйте еще раз запустить сервер командой npm run start
. Если ошибок нет, значит все хорошо. Теперь нам надо установить саму библиотеку Telegraf, которая упростит разработку бота. В терминале введите команду ниже и дождитесь установки.
Давайте научим бота приветствовать нас и что-то отвечать. Напишите следующее:
Давайте по порядку. На 6 строке мы создаем экземпляр класса Telegraf
(в качестве параметра передаем наш TOKEN) и кладем его в переменную bot
. По сути bot
– это объект, содержащий разные обработчики (middlewares) для обработки запросов. Что значат эти обработчики?
Взгляните на 8 строку. Здесь мы пишем обработчик, который сработает, когда пользователь введет команду /start
. То есть у объекта bot
мы вызываем метод start
, а в него передаем callback-функцию, которая принимает на вход один параметр - контекст (ctx
), и на выходе отправляет сообщение 'Welcome, bro'.
Аналогично строка 12. Этот обработчик будет реагировать на любой текст, введенный пользователем. Почему именно текст? Потому что мы явно в качестве первого параметра метода on
у объекта bot
указываем 'text'. Вторым параметром в on
мы передаем callback-функцию, которая также принимает ctx
и отвечает строкой 'just text'.
Очень важно не забыть запустить бота на 16 строке, вызвав метод launch()
.
Прежде, чем я познакомлю вас еще с некоторыми способами обработки, хочу обратить внимание. Порядок написания обработчиков имеет значение! Что я имею ввиду? Сейчас, если вы зайдете в своего бота и введете команду /start
, то будете получать сообщение 'Welcome, bro', а если будете вводить любой текст, то 'just text'. Теперь попробуйте поменять местами два этих обработчика и вы увидите, что введя команду /start
, вы получите 'just text'. И если вы введете любой текст, то получите этот же результат. Все потому, что обработчики проверяются сверху вниз и выполнится тот, который подходит под условие первым. Команда /start
– это по сути тот же текст, поэтому срабатывает bot.on('text')
. Убедившись в этом, можете вернуть порядок обработчиков обратно.
Давайте рассмотрим на что еще может реагировать бот? На самом деле на очень много вещей. Все их я сейчас физически не рассмотрю, но покажу принцип.
Мы можем передавать в метод on
разные тригеры, на которые будет реагировать бот и делать что-то полезное. Пока же он нам просто отвечает сообщением, но позже мы это поправим. На строке 17, к примеру, обработчик реагирует на присланные нами войс-сообщения и говорит, что у нас чудный голос. На 21 строке аналогично, только реакция на стикер. На 25 строке немного интереснее: если мы изменим какое-либо свое сообщение, бот среагирует на это. И на самом деле таких тригеров много. Ваш редактор обязательно вам подскажет, что можно вставить.
"А бот может реагировать не просто на сообщение, а на конкретный текст?" – спросите вы. Да. Смотрите:
На 13 строке мы у объекта bot
вызываем метод hears()
, куда передаем конкретный текст, на который хотим, чтобы бот среагировал и обработал. Теперь, если вы напишите боту "хочу есть", получить вразумительный ответ.
Также обратите внимание на строку 17. Если мы хотим обработать команду по типу /start
, то вызываем метод command
. В данном случае, если отправить боту /time
, то получите полную дату и время.
В принципе для начала этих обработчиков будет более, чем достаточно.
Давайте теперь рассмотрим клавиатуры, которые, кстати, хранить будем в файле keyboards.js (создайте его в корне вашего проекта). Клавиатуры могут быть как текстовые (при нажатии на кнопки вы отправляете боту текстовое сообщение) и инлайн (при нажатии вы отправляете callback_data). На примерах вам станет все яснее.
Перейдите в созданный файл keyboards.js и импортируйте класс Markup
вначале import Markup from 'telegraf/markup'
. Все клавиатуры будут обернуты в функции, которые мы будем экспортировать. Давайте для начала сделаем клавиатуру, которая будет иметь кнопки "Мои задачи" для просмотра текущих заданий, "Добавить задачу" для добавления новых задач и "Смотивируй меня" для мотивации. Вот что вам необходимо написать:
На 1 строке мы импортируем класс, предназначенный для клавиатур. На 3 строке создается функция getMainMenu()
, которая возвращает клавиатуру. Важно, ее надо экспортировать командой export
.
Так как нам нужно простое текстовое меню, мы на 4 строке вызываем у Markup метод keyboard()
. В него надо передать один массив []
. А в этот массив мы уже передаем массивы с нашими кнопками. Каждый такой массив соответствует ряду. В данном случае у нас клавиатура в два ряда, где в первом 2 кнопки, во втором 1.
На 7 строке мы вызываем метод resize()
. Он отвечает за размерность кнопок. Если его убрать, то в моб. версии будет что-то подобное:
Также очень важно вызвать метод extra()
, иначе клавиатура у вас просто не отобразится.
Итак, клавиатура готова, теперь можно ее использовать. В файле app.js нам следует импортировать (4 строка) нашу функцию getMainMenu()
. По логике нам надо, чтобы пользователь видел клавиатуру сразу, как только зашел в бота. Так как первым делом пользователь всегда запускает команду /start
, то давайте отправлять клавиатуру в этом обработчике.
Обработчик этой команды - это 9 строка. На 10 строке у объекта ctx мы вызываем метод reply()
, который позволяет боту отвечать пользователю. И если первым параметром мы передаем туда текст ответа, то следующим мы можем отправить нашу клавиатуру. Наша функция как раз таки и возвращает клавиатуру, поэтому можем смело вызвать ее там.
Если вы все сделали верно, то при команде /start у вас появляется клавиатура, при нажатии на которую мы отправляем боту определенный текст.
На самом деле это и есть ее главная задумка. Мы хотим получать от пользователя конкретные сообщения, и чтобы он их не писал руками, мы даем возможность сделать это одним кликом. Более того, мы с вами уже умеем писать обработчика, который должен реагировать на определенный текст. Это метод hears()
.
К слову, метод hears()
может принимать на вход не только строку, но и регулярное выражение и даже массив того и другого. Мы не будем сейчас усложнять процесс и для каждой кнопки клавиатуры напишем отдельные обработчики. Вот что у меня получилось:
На 13 и 17 строках пока что написаны обработчики, которые лишь отвечают текстом, но вскоре мы это поправим. Куда интереснее строка 21. Этот обработчик отвечает нам не текстом, а фотографией с подписью. Так вы можете заметить, что у объекта ctx
достаточно много методов и способов ответа. Первым параметром в replyWithPhoto()
мы передаем URL фотографии (я взял просто из инета). Вторым параметром мы передаем объект с прочими настройками. В данном случае поле caption – это описание фото (просто текст).
Давайте поработаем над логикой нашего бота. Задачи по-хорошему надо хранить в БД, но, опять же, не будем усложнять себе работу и сфокусируемся на боте. Пусть будет массив, имитирующий нашу базу данных, куда будет сохраняться каждая задача. Сама по себе задача будет JS строкой.
Давайте создадим файл db.js также в корне нашего проекта, и там создадим массив taskList
. Разумеется, не забываем его экспортировать.
Тепер в файле app.js напишем обработчик, который будет реагировать на любой текст.
Взгляните на код, ниже я поясняю все, что добавил.
Во-первых, на 10 строке я немного изменил обработку команды /start
. Вместо простого метода reply()
, я вызываю replyWithHTML()
. Как несложно догадаться, он позволяет нам использовать некоторые теги html, чтобы преображать текст. В частности, тегами <b> можно сделать текст жирным. Еще обратите внимание на часть строки \n
. Это перевод текста на новую строку. Соответственно, двумя такими \n
я перевожу на две строки вниз. Плюс, чтобы код помещался на экран, я строку разбил на две и просто произвожу конкатенацию строк оператором +
.
На 34 строке создан обработчик, реагирующий на любой текст. Напомню, что последовательность обработчиков имеет значение, поэтому он находится после всех. Логика работы здесь следующая: пользователь может что-то ввести по ошибке или же передумает добавлять новую задачу. Для этого перед добавлением мы будем дублировать его задачу и спрашивать, уверен ли он? В качестве ответа мы будем отображать инлайн клавиатуру с выбором ответов "да /нет".
Аналогично вызывается replyWithHTML()
, где происходит конкатенация строк. Но важно отметить, что кавычки здесь ``
специальные. В JS в подобные строки мы можем вставлять наши переменные путем вызова ${сюда наш объект}, что и происходит на строке 37. Через объект ctx
мы можем получить сообщение, которое пользователь отправил нам путем вызова ctx.message.text
.
На 38 строке передается клавиатура. Разумеется, ее необходимо создать в файле keyboards.js
Инлайн клавы создаются несколько иначе. Во-первых, у Markup вызывается соответсвующий метод inlineKeyboard()
. В него также передается массив, в который можно передавать как массив объектов кнопок, так и просто кнопки. В callbackButton()
нужно передать текст кнопки и callback_data. Эти 'yes' и 'no' будут являться триггерами при нажатии на них. Также вторым параметром в inlineKeyboard()
мы можем передать кол-во колонок, которое нам надо. И не забываем вызвать метод extra()
.
Сейчас, если боту что-то написать, как раз таки нам придет ответ типа
Как и отправляемый текст/войс/картинки/прочее, мы можем обрабатывать инлайн кнопки. Для этого у объекта bot
необходимо вызывать метод action()
. Взгляните на код:
На 47 строке мы пишем обработчик наших callback_data. Так как кода на обработку будет мало, мы вместо того, чтобы писать два отдельных обработчика под 'yes' и 'no', создадим один, куда можно передать массив строк. Однако, в зависимости от того, какая кнопка была нажата, нам надо совершить разные деяния. Если это было "no", то мы будем удалять последнее сообщение, которое прислал бот. То есть это будет как раз сообщение с вопросом о добавлении задачи. Если это было "yes", то нам надо добавить пользовательское сообщение (его задачу) в массив taskList (который находится в файле db.js). После можно отредактировать последнее сообщение, присланное ботом, сказав пользователю, что его задача успешно добавлена.
Как нам получить параметры кнопок? Все просто. Если к объекту ctx обратиться так ctx.callbackQuery.data
, то можно получить значение той кнопки, которая была нажата.
Вы спросите, что за функция addTask()
на 49 строке? И это справедливый вопрос. Это будет своего рода имитация добавления данных в реальную базу данных. Разумеется, функцию надо написать в файле db.js и импортировать в app.js (строка 5).
На 3 строке мы объявляем функцию и экспортируем ее. Функция принимает параметр text – текст сообщения, которое пользователь отправляет боту. На 4 строке мы добавляем text в массив методой push()
.
Теперь возникает небольшая головоломка. Когда пользователь отправляет боту сообщение, имеется один контекст ctx. Мы можем спокойно получить текст его сообщения командой ctx.message.text
. Однако после этого пользователь жмет на инлайн кнопки "да/нет", и в таком случае контекст перезаписывается. То есть, если сейчас попробовать посмотреть текст сообщения (как я показал выше) в обработчике на 47 строке (файл app.js), то вы получите ошибку. Как быть?
Мне на ум приходит следующее: пока ctx
имеет пользовательское сообщение, можно сохранит его глобально и потом получить там, где надо. Благо Telegraf любит разработчиков и дает такую возможность. Мы можем подключить "сессии", тем самым, будет возможность хранить что-то глобально. Для этого нужно написать следующее:
На 4 строке мы импортируем session. Так как это middleware, то его необходимо подключить до всех обработчиков, что мы и делаем на 11 строке. Думаю, вы помните, что на пользовательский текст срабатывает обработчик на строке 37. Поэтому в нем, на 38 строке мы создаем переменную ctx.session.taskText
и кладем в нее текст сообщения ctx.message.text
. Теперь мы сможем вызывать ctx.session.taskText
почти везде, где захотим. К слову, любую переменную сессии вы можете создать по форме ctx.session.<название переменной>
.
Наконец, на 49 строке мы передаем эту переменную в качестве параметра функции addTask
. Если вы все сделали правильно, то добавление должно работать, правда визуально мы не можем посмотреть задачи. Можете в функцию addTask в самом конце добавить console.log(taskList)
и посмотреть в консоле.
Как я и говорил, наш файл db.js служит некой имитацией работы с реальной базой. Однако в жизни запросы к БД – это асинхронные операции, которые могут занимать некоторое время. Это значит, к примеру, если мы пишем запрос типа getDataFromDB(), то он не выполнится именно в тот момент, когда будет вызван. Вдруг там запрос на получение миллиона записей, и он может занять пару секунд. Если весь оставшийся код будет ждать, пока выполнится такой запрос, будет не очень круто. И уже тем более, если будет ждать сам пользователь.
В общем давайте добавим функцию, которая позволит получить данные массива taskList.
На 3 строке создаем и экспортируем функцию getMyTasks()
. Эта функция будет возвращать так называемый промис, почитать о которых вы можете здесь. В промис передается функция обратного вызова с одним параметром resolve. Внутри промиса также вызывается функция setTimeout()
. Она позволяет вызывать какую-либо функцию через определенный интервал. Пусть это будет 500 миллисекунд. А вызывать она будет resolve()
с нашим массивом taskList. Здесь довольно много кода, но весь он направлен на то, чтобы мы смогли асинхронно получить массив с задачами через 500 миллисекунд.
Теперь надо нашу функцию импортировать в app.js и вызывать в соответствующем обработчике.
Обработчик на 20 строке претерпел изменения! Во-первых, наша callback-функция с одним параметром ctx заимела оператор async и выглядит теперь async ctx => {...}
. Подробнее про async/await вы сможете почитать здесь. Я лишь скажу, что эти операторы упрощают работу с асинхронным кодом. Так как функция для получения всех задач возвращает промис, то нам надо его обработать. Для обработки промисов есть несколько подходов, но в нашем примере будет использоваться оператор await
. Однако чтобы использовать этот оператор внутри функций, сами функции должны быть явно объявлены с оператором async. По этой причине он и был добавлен.
На 21 строке мы вызываем функцию getMyTasks()
с оператором await (конечно, не забываем импортировать ее на строке 6), что дает нам на выходе массив, который мы сохраняем в переменную tasks
.
Так как показывать пользователю массив не лучший вариант, мы каждый его элемент будем в виде строки сохранять в одну общую строку result
(создаем на 22 строке). Для того, чтобы пробежаться по массиву в JS, можно использовать простой цикл for. Чтобы каждая задача имела свой уникальный номер, мы просто будем выводить ее индекс из массива задач, увеличенный на единицу (увеличиваем т.к. индексация начинается с нуля, а не единицы).
После чего на 28 строке при помощи replyWithHTML()
выводим весь список задач. Попробуйте запустить сервер, добавьте пару задач и нажмите на "Мои задачи". Если все работает, то поздравляю вас! Осталось только сделать удаление задач и на этом можно заканчивать.
Кнопка "Добавить задачу" по факту оказалась бесполезной. Предлагаю заменить ее на "Удалить задачу". Для этого поменяйте текст в файле keyboards.js, а также текст обработчика на 34 строке в файле app.js. Чтобы изменения вступили в силу, необходимо в боте написать /start
.
Пусть логика будет простая: удалить задачу можно будет только если отправить боту сообщение "удалить <порядковый номер задачи>". При нажатии на кнопку "Удалить задачу" мы будем сообщать пользователю об той инструкции. Выглядеть этот обработчик будет так:
Для определения ключевой фразы удаления задач можно использовать регулярные выражения. Пусть после обработчика "Удалить задачу" будет новый обработчик, который будет реагировать на нужный нам текст.
На 10 строке у нас идет как раз таки регулярное выражение. На 11 строке мы берем ctx.message.text
, где содержится текст, к примеру, "удалить 2". Чтобы из него вычленить цифру, а именно она нам и нужна, используется еще одно регулярное выражение +/\d+/.exec()
. Но эта цифра имеет строковый тип, а нам нужен целочисленный, поэтому мы преобразовываем при помощи Number()
. И теперь от числа можем смело отнимать единицу. Это нужно затем, что выше мы прибавляли единицу, чтобы нумерация задач для пользователя отображалась с 1. Индексы же массива начинаются с нуля, и чтобы правильно по ним удалять, нужно снова отнять единицу. Весь полученный результат сохраняется в переменную id
.
На 12 строке вызывается метод deleteTask()
, в который надо передать id. Разумеется, его надо написать. Для этого в файле db.js добавим следующее:
На 15 строке создается и экспортируется функция для удаления, которая на вход принимает параметр id
. В нем просто идет вызов метода splice()
, который позволяет удалять элементы у массивов в JS.
После создания этого метода не забудьте импортировать его в файле app.js. Итоговый вариант должен выглядеть так:
Друзья! Могу вас поздравить с вполне рабочим ботом. Давайте подведем итог, что было сделано: Вы научились настраивать свое рабочее окружение (использовать ноду для своих JS проектов). Что касается ботов, то вы узнали про несколько способов обработки пользовательских действий. Научились создавать два типа клавиатур. Поработали с асинхронностью и регулярными выражениями. Также посмотрели, как можно отправить фотографии с описанием. Осталось, разве что только задеплоить бота на рабочий сервер, но об этом в другой статье.
С практической точки зрения бот не очень много пользы несет. Однако его создание позволило охватить много базовых подходов, используемых при разработке телеграм ботов. С этими знаниями вы уже можете создавать свои простые и рабочие решения.
Исходники бота на гитхабе.
Будем надеятся, что это не последняя моя статья. Если она оказалась для вас полезной или же остались какие-либо вопросы, вы всегда можете связаться со мной в телеграме arutemu_su.
Написано специально для телеграм канала Debug_Yourself. Можете подписаться на канал, ведь там я рассказываю про свой путь становления программистом. Делюсь информацией, которую изучаю, проектами, которые делаю, шишками, которые набиваю. Также иногда проскакивает диванная философия.