Библиотека python-telegram-bot в Python
Пакет python-telegram-bot содержит ряд высокоуровневых классов, которые делают разработку ботов простой и понятной. Эти классы содержатся в модуле telegram.ext . Он совместим с версиями Python 3.7+. Пакет python-telegram-bot также может работать с PyPy3 (официально не поддерживается), хотя раньше было много проблем.
Внимание! Пакеты python-telegram-bot версии 13.x будут придерживаться многопоточной парадигмы программирования (на данный момент актуальна версия 13.15). Пакеты версий 20.x и новее предоставляют чистый асинхронный Python интерфейс для Telegram Bot API. Дополнительно смотрите основные изменения в пакете python-telegram-bot версии 20.x.
Установка пакета python-telegram-bot в виртуальное окружение:
Пакет python-telegram-bot пытается использовать как можно меньше сторонних зависимостей. Однако для некоторых функций использование сторонней библиотеки более разумно, чем повторная реализация функциональности. Поскольку эти функции являются необязательными, соответствующие сторонние зависимости не устанавливаются по умолчанию. При установке пакета их можно перечислены как необязательные зависимости. Это позволяет избежать ненужных конфликтов зависимостей для пользователей, которым не нужны дополнительные функции.
Единственная необходимая зависимость — это httpx ~= 0.23.3 для telegram.request.HTTPXRequest , сетевого бэкенда по умолчанию.
# создаем виртуальное окружение, если нет $ python3 -m venv .telegram --prompt TelegramBot # активируем виртуальное окружение $ source .telegram/bin/activate # ставим последнюю многопоточную версию python-telegram-bot (TelegramBot):~$ python3 -m pip install python-telegram-bot==13.15 -U # или установка асинхронной версии со всеми зависимостями (TelegramBot):~$ python3 -m pip install python-telegram-bot[all] -U # устанавливает все необязательные зависимости расширения `telegram.ext` (TelegramBot):~$ python3 -m pip install python-telegram-bot[ext] -U # При установке зависимости можно перечислить отдельно как: # python-telegram-bot[rate-limiter,webhooks,callback-data,job-queue]
- pip install python-telegram-bot[passport] — устанавливает библиотеку cryptography . Используется для функции, связанных с Telegram Passport.
- pip install python-telegram-bot[socks] — устанавливает httpx[socks] . Используется для работы с сервером Socks5.
- pip install python-telegram-bot[http2] — устанавливает httpx[http2] . Используется для работы с сервером HTTP/2.
- pip install python-telegram-bot[rate-limiter] — устанавливает aiolimiter . Используется для работы с telegram.ext.AIORateLimiter .
- pip install python-telegram-bot[webhooks] — устанавливает библиотеку tornado . Используется для работы с telegram.ext.Updater.start_webhook / telegram.ext.Application.run_webhook .
- pip install python-telegram-bot[callback-data] — устанавливает библиотеку cachetools . Используется для работы с произвольными callback_data .
- pip install python-telegram-bot[job-queue] — устанавливает библиотеку APScheduler и применяет pytz , где pytz является зависимостью APScheduler . Используется для работы с telegram.ext.JobQueue .
Чтобы установить несколько необязательных зависимостей, разделите их запятыми, например. pip install python-telegram-bot[socks,webhooks] .
Пакет python-telegram-bot в основном будет разбираться на примерах. Содержание, обзорного/вводного материала по библиотеке ниже. Меню с материалами по всему разделу — справа.
Высокоуровневый интерфейс пакета python-telegram-bot построен поверх чистой реализации Telegram Bot API и находится в подмодуле расширений telegram.ext . Он предоставляет простой в использовании интерфейс и снимает с программиста некоторую работу.
Для выполнения примеров требуется сгенерировать токен доступа к API. Для этого необходимо пообщаться с @BotFather и выполнить несколько простых шагов, описанных в разделе Команды и оповещения @BotFather в Telegram.
Содержание:
- Многопоточный модуль расширения telegram.ext (версия 13.x);
- Создание Telegram bot, шаг за шагом (версия 13.x);
- Режим встроенных запросов (версия 13.x);
- Весь код созданного многопоточного бота.
- Создание асинхронного Telegram bot, шаг за шагом (версия 20.x);
- Режим встроенных запросов (версия 20.x);
Многопоточный модуль расширения telegram.ext (версия 13.x).
Модуль расширений telegram.ext состоит из нескольких классов, но два наиболее важных — это telegram.ext.Updater и telegram.ext.Dispatcher .
Класс Updater постоянно слушает сервер Telegram, получает новые сообщения и передает их классу Dispatcher . Если создать объект Updater , то он автоматически создаст Dispatcher и свяжет их вместе с очередью. Затем в объекте Dispatcher можно зарегистрировать обработчики разных типов, которые будут сортировать полученные объектом Updater сообщения. Поступающие сообщения будут обрабатываться в соответствии с зарегистрированными обработчиками и передавать их в функцию обратного вызова, которую необходимо определить.
Еще нужно знать и понимать, что экземпляр Updater реализует все методы класса telegram.Bot (API Telegram), которые будут связаны с данным Updater . У экземпляра Dispatcher , в свою очередь, есть так называемый контекст context , который, при регистрации любого обработчика сообщений передается в функцию обратного вызова этого обработчика (кстати в нее так же передается updater ). Так вот, у этого контекста то же есть экземпляр класса telegram.Bot , только он связан с конкретным сообщением, которое попало в эту функцию обратного вызова.
Каждый обработчик является экземпляром подкласса класса telegram.ext.Handler . Пакет python-telegram-bot предоставляет классы обработчиков почти на все стандартные случаи, но если нужно что-то конкретное, то можно создать собственный обработчик, наследуясь от класса Handler .
Создание Telegram bot, шаг за шагом.
Во-первых, нужно создать объект Updater . В коде ниже замените константу TOKEN на API-токен вашего бота. Для более быстрого доступа к Dispatcher , в который Updater посылает сообщение, можно создать его отдельно:
from telegram.ext import Updater TOKEN = 'Замените эту строку на token, полученный от @BotFather' # получаем экземпляр `Updater` updater = Updater(token=TOKEN, use_context=True) # получаем экземпляр `Dispatcher` dispatcher = updater.dispatcher
Примечание. Аргумент use_context=True (по умолчанию False ) — это специальный аргумент, необходимый только для python-telegram-bot меньше версии 12.x. Это обеспечивает лучшую обратную совместимость со старыми версиями и дает пользователям время для обновления. Начиная с версии 13.x, значение аргумента use_context=True используется по умолчанию (указывать его не нужно).
Чтобы знать, когда и почему что-то не работает должным образом, настроим модуль ведения журнала логов:
import logging logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO)
Примечание. если хотите узнать больше об обработке исключений с python-telegram-bot , прочтите подраздел об «Обработка исключений».
Теперь определим функцию, которая должна обрабатывать определенный тип сообщения, отправленных боту:
# Обратите внимание, что из обработчика в функцию # передаются экземпляры `update` и `context` def start(update, context): # `bot.send_message` это метод Telegram API # `update.effective_chat.id` - определяем `id` чата, # откуда прилетело сообщение context.bot.send_message(chat_id=update.effective_chat.id, text="I'm a bot, please talk to me!")
- аргументы update и context передаются автоматически;
- update — это объект связанный с экземпляром Update который присылает и отправляет все сообщения. Через него можно получить доступ к экземпляру telegram.Bot() как update.bot ;
- context — это объект связанный с контекстом обработанного сообщения. Через него также можно получить доступ к экземпляру telegram.Bot() как context.bot .
Цель состоит в том, чтобы эта функция вызывалась каждый раз, когда бот получает сообщение с серверов Telegram, содержащее команду /start . Для этого можно использовать класс CommandHandler (один из предоставленных подклассов Handler ) и зарегистрировать его в Dispatcher :
# импортируем обработчик CommandHandler, # который фильтрует сообщения с командами from telegram.ext import CommandHandler # говорим обработчику, если увидишь команду `/start`, # то вызови функцию `start()` start_handler = CommandHandler('start', start) # добавляем этот обработчик в `dispatcher` dispatcher.add_handler(start_handler)
И это все, что нужно! Для запуска бота дописываем команду:
# говорим экземпляру `Updater`, # слушай сервера Telegram. updater.start_polling()
Начните чат со своим ботом и введите команду /start — если все пойдет хорошо, он ответит.
Созданный бот может отвечать только на команду /start . Добавим еще один обработчик, который прослушивает обычные сообщения. Для этого используем класс MessageHandler — другой подкласс Handler , для вывода всех текстовых сообщений:
# функция обратного вызова def echo(update, context): # добавим в начало полученного сообщения строку 'ECHO: ' text = 'ECHO: ' + update.message.text # `update.effective_chat.id` - определяем `id` чата, # откуда прилетело сообщение context.bot.send_message(chat_id=update.effective_chat.id, text=text) # импортируем обработчик `MessageHandler` и класс с фильтрами from telegram.ext import MessageHandler, Filters # говорим обработчику `MessageHandler`, если увидишь текстовое # сообщение (фильтр `Filters.text`) и это будет не команда # (фильтр ~Filters.command), то вызови функцию `echo()` echo_handler = MessageHandler(Filters.text & (~Filters.command), echo) # регистрируем обработчик `echo_handler` в экземпляре `dispatcher` dispatcher.add_handler(echo_handler)
С этого момента создаваемый бот должен обрабатывать все получаемые текстовые сообщения, а так же работать с командой /start , но не будет реагировать на любые другие команды (например, /your_command ) .
Примечание: как только новые обработчики добавляются в диспетчер, они сразу вступают в силу.
Примечание. Класс telegram.ext.Filters содержит ряд так называемых фильтров, которые фильтруют входящие сообщения по тексту, изображениям, обновлениям статуса и т. д. Любое сообщение, которое возвращает True хотя бы для одного из фильтров, переданных в MessageHandler , будет принято. Если необходимо, то можно написать свои собственные фильтры. Подробнее смотрите раздел «Все о фильтрации сообщений python-telegram-bot в Python».
Добавим боту другую функциональность и реализуем команду /caps , которая будет принимать какой-то текст в качестве аргумента и отвечать на него тем же текстом, только в верхнем регистре. Аргументы команды (например /caps any args ) будут поступать в функцию обратного вызова в виде списка [‘any’, ‘args’] , разделенного по пробелам:
def caps(update, context): # если аргументы присутствуют if context.args: # объединяем список в строку и # переводим ее в верхний регистр text_caps = ' '.join(context.args).upper() # `update.effective_chat.id` - определяем `id` чата, # откуда прилетело сообщение context.bot.send_message(chat_id=update.effective_chat.id, text=text_caps) else: # если в команде не указан аргумент context.bot.send_message(chat_id=update.effective_chat.id, text='No command argument') context.bot.send_message(chat_id=update.effective_chat.id, text='send: /caps argument') # обработчик команды '/caps' caps_handler = CommandHandler('caps', caps) # регистрируем обработчик в диспетчере dispatcher.add_handler(caps_handler)
Примечание. Обратите внимание на использование context.args . Объект CallbackContext будет иметь много разных атрибутов в зависимости от того, какой обработчик используется.
Режим встроенных запросов.
Еще одна интересная особенность официального Telegram Bot API — это режим встроенных запросов к ботам. Помимо отправки команд в личных сообщениях или группах, пользователи могут взаимодействовать с ботом с помощью встроенных запросов. Если встроенные запросы включены, то пользователи могут вызвать бота, введя его имя @bot_username и запрос в поле ввода текста в любом чате. Запрос отправляется боту в обновлении. Таким образом, люди могут запрашивать контент у ботов в любом из своих чатов, групп или каналов, вообще не отправляя им никаких отдельных сообщений.
Если необходимо реализовать такую функциональность для своего бота, то сначала необходимо изменить конфигурацию в @BotFather , включив этот режим при помощи команды /setinline . Иногда требуется какое-то время, пока бот не зарегистрируется в качестве встроенного бота на вашем клиенте. Можно ускорить процесс, перезапустив приложение Telegram или иногда просто нужно немного подождать.
Здесь используется ряд новых типов:
from telegram import InlineQueryResultArticle, InputTextMessageContent def inline_caps(update, context): query = update.inline_query.query if not query: return results = list() results.append( InlineQueryResultArticle( id=query.upper(), title='Convert to UPPER TEXT', input_message_content=InputTextMessageContent(query.upper()) ) ) context.bot.answer_inline_query(update.inline_query.id, results) from telegram.ext import InlineQueryHandler inline_caps_handler = InlineQueryHandler(inline_caps) dispatcher.add_handler(inline_caps_handler)
Теперь бот может работать и через режим встроенных запросов.
Пользователи могут попытаться отправить боту команды, которые он не понимает, поэтому можно использовать обработчик MessageHandler с фильтром Filters.command , чтобы отвечать на все команды, которые не были распознаны предыдущими обработчиками.
def unknown(update, context): context.bot.send_message(chat_id=update.effective_chat.id, text="Sorry, I didn't understand that command.") unknown_handler = MessageHandler(Filters.command, unknown) dispatcher.add_handler(unknown_handler)
Примечание. Этот обработчик должен быть добавлен последним. Если его поставить первым, то он будет срабатывать до того, как обработчик CommandHandlers для команды /start увидит обновление. После обработки обновления функцией unknown() все дальнейшие обработчики будут игнорироваться.
Чтобы обойти такое поведение, можно передать в метод dispatcher.add_handler(handler, group) , помимо самой функции обработчика аргумент group со значением, отличным от 0. Аргумент group можно воспринимать как число, которое указывает приоритет обновления обработчика. Более низкая группа означает более высокий приоритет. Обновление может обрабатываться (максимум) одним обработчиком в каждой группе.
Остановить бота можно командой updater.stop() .
Примечание. Объект Updater запускается в отдельном потоке, это хорошо, если вы запускаете команды в интерпретаторе Python. Но если запустить скрипт с написанным ботом, то вероятно, удобнее будет останавливать бота, нажатием Ctrl + C , отправив сигнал процессу бота. Для этого, после запуска бота командой updater.start_polling() допишите в коде следующей строкой команду updater.idle() .
Весь код созданного бота:
# sample-bot.py from telegram import InlineQueryResultArticle, InputTextMessageContent from telegram.ext import Updater, CommandHandler from telegram.ext import MessageHandler, Filters, InlineQueryHandler TOKEN = 'Замените эту строку на token, полученный от @BotFather' updater = Updater(token=TOKEN) dispatcher = updater.dispatcher # функция обработки команды '/start' def start(update, context): context.bot.send_message(chat_id=update.effective_chat.id, text="I'm a bot, please talk to me!") # функция обработки текстовых сообщений def echo(update, context): text = 'ECHO: ' + update.message.text context.bot.send_message(chat_id=update.effective_chat.id, text=text) # функция обработки команды '/caps' def caps(update, context): if context.args: text_caps = ' '.join(context.args).upper() context.bot.send_message(chat_id=update.effective_chat.id, text=text_caps) else: context.bot.send_message(chat_id=update.effective_chat.id, text='No command argument') context.bot.send_message(chat_id=update.effective_chat.id, text='send: /caps argument') # функция обработки встроенного запроса def inline_caps(update, context): query = update.inline_query.query if not query: return results = list() results.append( InlineQueryResultArticle( id=query.upper(), title='Convert to UPPER TEXT', input_message_content=InputTextMessageContent(query.upper()) ) ) context.bot.answer_inline_query(update.inline_query.id, results) # функция обработки не распознных команд def unknown(update, context): context.bot.send_message(chat_id=update.effective_chat.id, text="Sorry, I didn't understand that command.") # обработчик команды '/start' start_handler = CommandHandler('start', start) dispatcher.add_handler(start_handler) # обработчик текстовых сообщений echo_handler = MessageHandler(Filters.text & (~Filters.command), echo) dispatcher.add_handler(echo_handler) # обработчик команды '/caps' caps_handler = CommandHandler('caps', caps) dispatcher.add_handler(caps_handler) # обработчик встроенных запросов inline_caps_handler = InlineQueryHandler(inline_caps) dispatcher.add_handler(inline_caps_handler) # обработчик не распознанных команд unknown_handler = MessageHandler(Filters.command, unknown) dispatcher.add_handler(unknown_handler) # запуск прослушивания сообщений updater.start_polling() # обработчик нажатия Ctrl+C updater.idle()
Асинхронный модуль расширения telegram.ext (версия 20.x).
Асинхронный модуль расширений telegram.ext состоит из нескольких классов, но здесь самый важный — это telegram.ext.Application .
Класс Application отвечает за получение обновлений из очереди update_queue , где класс Updater постоянно получает новые обновления из Telegram и добавляет их в эту очередь. Если создать объект приложения с помощью ApplicationBuilder , то он автоматически создаст средство обновления и свяжет их вместе с помощью asyncio.Queues . Затем можно зарегистрировать обработчики различных типов в приложении, которое будут сортировать обновления, полученные средством обновления, в соответствии с зарегистрированными обработчиками и доставлять их в определенную функцию обратного вызова.
Каждый обработчик является экземпляром любого подкласса класса telegram.ext.BaseHandler . Библиотека предоставляет классы-обработчики практически для всех случаев использования, но если нужно что-то очень конкретное, то можете сами создать подкласс Handler .
Создание асинхронного Telegram bot, шаг за шагом.
Первое. В описании работы асинхронного бота, в коде будут комментироваться только отличительные моменты. Если что-то непонятно — смотрим сначала создание многопоточного бота.
Второе. для выполнения примеров создадим новый файл, например sync-bot.py . По ходу материала в этот файл будем добавлять новый код несколько раз. Другими словами, для краткости не будем повторять весь код создаваемого бота каждый раз, когда что-то добавляем.
И третье, самое важное. Для создания асинхронного бота (без наворотов) достаточно понимать и учитывать в коде следующие вещи:
- Все функции-обработчики становятся сопрограммами, т.е. добавляется асинхронный оператор async перед определением функции-обработчика.
- Внутри функции-обработчика появляется асинхронный оператор await , который ставится перед методами объектов context или update , ожидающие своей очереди для каких-то сетевых операций (например, отправить/ответить/изменить сообщение и т.д.). Объекты context или update передаются в качестве аргументов функциям-обработчикам. Важно! Здесь нужно четко понимать какие методы ожидают подключения к сети. Например, метод context.bot.send_message() или update.message.reply_text() явно хотят что-то отправить по сети в чат, следовательно перед ним ставиться await . А вот свойство update.message.chat_id или update.effective_chat.id просто извлекают значение chat_id из словаря, полученного во время фоновой операции Update и не являются сопрограммами — перед ними оператор await НЕ СТАВИТЬСЯ.
- Создание экземпляра приложения Telegram осуществляется вызовом одного класса ApplicationBuilder() или Application.builder() , а настройка (параметров по умолчанию) происходит вызовом цепочки методов этого экземпляра.
И так, вставим в файл следующий код:
import logging from telegram import Update from telegram.ext import ApplicationBuilder, ContextTypes, CommandHandler # настроим модуль ведения журнала логов logging.basicConfig( format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO ) # определяем асинхронную функцию async def start(update, context): # ожидание отправки сообщения по сети - нужен `await` await context.bot.send_message(chat_id=update.effective_chat.id, text="I'm a bot, please talk to me!") if __name__ == '__main__': TOKEN = 'Замените эту строку на token, полученный от @BotFather' # создание экземпляра бота через `ApplicationBuilder` application = ApplicationBuilder().token(TOKEN).build() # создаем обработчик для команды '/start' start_handler = CommandHandler('start', start) # регистрируем обработчик в приложение application.add_handler(start_handler) # запускаем приложение application.run_polling()
Переварим написанный код, пройдемся по нему шаг за шагом.
import logging # настройки модуля ведения журнала logging.basicConfig( format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO )
Вышеуказанный код предназначена для настройки модуля ведения журнала, чтобы знать, когда (и почему) что-то не работает должным образом:
TOKEN = 'Замените эту строку на token, полученный от @BotFather' # создание экземпляра бота через `ApplicationBuilder` application = ApplicationBuilder().token(TOKEN).build()
Здесь создается объект Application . Константу TOKEN необходимо заменить API-токеном своего бота.
Приложение само по себе ничего не делает. Чтобы добавить функциональность, необходимо сделать две вещи. Во-первых, определить функцию, которая должна обрабатывать определенный тип обновления:
async def start(update, context): await context.bot.send_message( chat_id=update.effective_chat.id, text="I'm a bot, please talk to me!" )
- аргументы update и context передаются автоматически;
- update — это объект связанный с экземпляром Update который присылает и отправляет все сообщения. Через него можно получить доступ к экземпляру telegram.Bot() как update.bot ;
- context — это объект связанный с контекстом обработанного сообщения. Через него также можно получить доступ к экземпляру telegram.Bot() как context.bot .
Цель состоит в том, чтобы асинхронная функция start() вызывалась каждый раз, когда бот получает сообщение от сервера Telegram, содержащее команду /start . Для этого можно использовать один из обработчиков CommandHandler (подклассов Handler ) и зарегистрировать его в приложении:
from telegram.ext import CommandHandler # регистрируем обработчик команды 'start' start_handler = CommandHandler('start', start) # добавляем обработчик в приложение application.add_handler(start_handler)
И это все, что на данном этапе нужно. И наконец, строка application.run_polling() запускает бота, пока не поступит нажмете CTRL+C .
Теперь попробуем. В чат со своим ботом введем команду /start — если все прошло правильно, он ответит.
Теперь бот умеет отвечать только на команду /start . Добавим еще один обработчик, который прослушивает обычные сообщения. Для этого используем класс обработчика MessageHandler (подкласс Handler ) для отображения всех текстовых сообщений. Остановим запущенного бота ( CTRL+C ), определим новую функцию echo() , добавим ее в обработчик MessageHandler , а созданный обработчик зарегистрируем в приложении:
from telegram import Update from telegram.ext import filters, MessageHandler, ApplicationBuilder, CommandHandler, ContextTypes async def start(update, context): . async def echo(update, context): await context.bot.send_message(chat_id=update.effective_chat.id, text=update.message.text) if __name__ == '__main__': . # создаем обработчик текстовых сообщений, # которые будут поступать в функцию `echo()` echo_handler = MessageHandler(filters.TEXT & (~filters.COMMAND), echo) # регистрируем обработчик для `start()` application.add_handler(start_handler) # регистрируем обработчик для `echo()` application.add_handler(echo_handler) # запускаем приложение application.run_polling()
С этого момента бот должен повторять все получаемые им текстовые (некомандные) сообщения.
Примечание. Модуль filters содержит ряд так называемых фильтров, которые фильтруют входящие сообщения по тексту, изображениям, обновлениям статуса и многому другому. Любое сообщение, возвращающее True хотя бы для одного из фильтров, переданных в MessageHandler , будет принято. Можно написать свои собственные фильтры, если это необходимо.
Добавим боту некоторые реальные функции. Реализуем команду /caps , которая будет принимать некоторый текст в качестве аргумента и отвечать на него копией сообщения в верхнем регистре. Чтобы упростить задачу, будем получать аргументы команды в виде списка, разделенного пробелами, которые передаются команде /caps в функцию обратного вызова caps() :
. async def caps(update, context): text_caps = ' '.join(context.args).upper() await context.bot.send_message(chat_id=update.effective_chat.id, text=text_caps) if __name__ == '__main__': . # создаем новый обработчик для функции `caps()` caps_handler = CommandHandler('caps', caps) # здесь регистрируются созданные обработчики application.add_handler(start_handler) application.add_handler(echo_handler) # вот регистрация для функции `caps()` application.add_handler(caps_handler) # запускаем приложение application.run_polling()
Примечание. Обратите внимание на использование context.args . CallbackContext будет иметь несколько атрибутов, в зависимости от того, какой обработчик используется.
Режим встроенных запросов.
Еще одна интересная функция Telegram Bot API — режим встроенных запросов. Если необходимо реализовать такую функциональность для своего бота, то сначала необходимо изменить конфигурацию в @BotFather , включив этот режим при помощи команды /setinline . Иногда требуется какое-то время, пока бот не зарегистрируется в качестве встроенного бота на вашем клиенте. Можно ускорить процесс, перезапустив приложение Telegram или иногда просто нужно немного подождать.
Здесь используется ряд новых типов, так что смотрим внимательно (комментировать код не имеет смысла, т.к. просматривается аналогия с предыдущим кодом):
from telegram import InlineQueryResultArticle, InputTextMessageContent from telegram.ext import InlineQueryHandler . async def inline_caps(update, context): query = update.inline_query.query if not query: return results = [] results.append( InlineQueryResultArticle( id=query.upper(), title='Caps', input_message_content=InputTextMessageContent(query.upper()) ) ) await context.bot.answer_inline_query(update.inline_query.id, results) if __name__ == '__main__': . # создаем обработчик для функции `inline_caps()` inline_caps_handler = InlineQueryHandler(inline_caps) # регистрируем обработчик application.add_handler(inline_caps_handler) application.run_polling()
Теперь бот может отвечать копией сообщения в верхнем регистре и через режим встроенных запросов.
Некоторые сбитые с толку пользователи могут попытаться отправить созданному боту команды, которые он не понимает, поэтому можно использовать MessageHandler с фильтром filters.COMMAND , чтобы отвечать на все команды, которые не были распознаны предыдущими обработчиками.
. async def unknown(update, context): await context.bot.send_message(chat_id=update.effective_chat.id, text="Sorry, I didn't understand that command.") if __name__ == '__main__': . # создаем обработчик для функции `unknown()` unknown_handler = MessageHandler(filters.COMMAND, unknown) # регистрируем обработчик application.add_handler(unknown_handler) application.run_polling()
Примечание. Этот обработчик должен быть добавлен последним. Если его поставить первым, то он будет срабатывать до того, как обработчик CommandHandlers увидит обновление. После обработки обновления функцией unknown() все дальнейшие обработчики будут игнорируются.
Чтобы обойти такое поведение, можно передать в метод dispatcher.add_handler(handler, group) , помимо самой функции обработчика аргумент group со значением, отличным от 0. Аргумент group можно воспринимать как число, которое указывает приоритет обновления обработчика. Более низкая группа означает более высокий приоритет. Обновление может обрабатываться (максимум) одним обработчиком в каждой группе.
- КРАТКИЙ ОБЗОР МАТЕРИАЛА.
- Переход на асинхронный python-telegram-bot версии 20.x
- Чистый интерфейс Python для Telegram Bot API
- Команды и оповещения @BotFather в Telegram
- Обработка сообщений модулем python-telegram-bot
- Фильтры сообщений модуля python-telegram-bot
- Хранение временных данных модулем python-telegram-bot
- Настройки по умолчанию модуля python-telegram-bot
- Планировщик сообщений модуля python-telegram-bot
- Форматирование и отправка сообщений в python-telegram-bot
- Работа с файлами/media, модуль python-telegram-bot
- Меню из кнопок, модуль python-telegram-bot
- Объект CallbackContext модуля python-telegram-bot
- Подключения Telegram-бота через webhook
- Обработка исключений модуля python-telegram-bot
- Создание Inline-бота, модуль python-telegram-bot
- Работа с опросами в модуле python-telegram-bot
- Создание разговоров ConversationHandler в python-telegram-bot
- Перезапуск телеграмм-бота в случае ошибки
- Декоратор-обработчик сообщений в python-telegram-bot
- Авторизация на сайте через Telegram Passport
- Ведение публикаций в Telegram-канале с python-telegram-bot
- UTF коды emoji/эмодзи для отправки в Telegram из Python
Урок №2. Тело telegram бота
В этом уроке мы научимся создавать проект, устанавливать библиотеки. Также мы создадим отдельный файл, который не пушиться на сервер и хранится локально на нашем компьютере, и где будут храниться наш секретный токен, пароли и другое. Также мы подключим нашего telegram бота к python для дальнейшей работы с ним.
Создание проекта.
Я буду использовать программу PyCharm. В ней вам необходимо создать новый проект с любым названием(File -> New project -> Create). Я назову проект pythontelegram.Также внутри проекта необходимо создать новый файл для написания кода, название может быть любым(внутри проекта нажимаем правой кнопкой мыши -> New -> Python File)
Чат-боты в Telegram на Python и Aiogram: пишем первого бота
Большой гайд по работе с Aiogram на примере эхо‑бота — чат‑бота, который повторяет за пользователем его фразы. Весь код — внутри статьи.


Иллюстрация: Polina Vari для Skillbox Media

Антон Яценко
Изучает Python, его библиотеки и занимается анализом данных. Любит путешествовать в горах.Компании используют чат-ботов в Telegram для разных задач: рассылают новости о текущих акциях, принимают платежи или даже организуют службы технической поддержки. Обычные пользователи тоже применяют их для своих бытовых нужд — ведут учёт личных финансов или оформляют посты в социальных сетях.
Благодаря этой статье вы научитесь с нуля создавать чат-ботов с помощью Python и библиотеки Aiogram. Мы напишем эхо-бота, который отвечает на сообщения пользователя точно такими же сообщениями. Это первая часть урока по Aiogram — во второй части мы добавим боту кнопки и новые фичи.
Содержание
- Библиотеки для создания бота
- Краткое описание Aiogram
- Создаём эхо-бота
- Шаг 1. Устанавливаем Python
- Шаг 2. Создаём виртуальное окружение
- Шаг 3. Создаём бота
- Шаг 4. Подключаем Aiogram
- Шаг 5. Пишем код для эхо-бота
- Шаг 6. Запускаем бота и проверяем работу
- Что дальше?
Библиотеки для создания бота
Для создания телеграм-ботов на Python существует несколько десятков библиотек. Они различаются популярностью, размером комьюнити и функциональностью. Рассмотрим самые популярные.
Aiogram. Современная библиотека, набирающая популярность: многие чат-боты написаны на ней. В этой и последующих статьях цикла мы будем работать именно с Aiogram. Библиотека реализует асинхронное выполнение кода, что позволяет не останавливать работу бота в ожидании ответа пользователя. Кроме того, у Aiogram есть подробная документация и большое русскоязычное комьюнити.
Python-telegram-bot. Одна из первых библиотек для создания ботов. Отличается от Aiogram синхронным подходом к работе, то есть при ожидании ответа от пользователя выполнение кода останавливается.
TeleBot. Библиотека для создания простых ботов, позволяющая работать с асинхронным и синхронным подходом на выбор. Подходит для небольших проектов. Подробнее можно узнать в документации.
Что нужно знать об Aiogram перед написанием кода
Перед тем как приступить к написанию нашего бота, остановимся подробнее на одной технической особенности Aiogram.
Как уже было сказано ранее, одно из главных достоинств библиотеки — полная асинхронность. Она использует синтаксис async/await, который позволяет программе выполнять несколько задач одновременно и эффективно управлять потоком выполнения.
Вот простой пример функции, использующей механизм async/await:
Другой плюс Aiogram — в большом наборе инструментов и хуков, которые можно использовать для добавления дополнительных функций и настроек бота. Библиотека обеспечивает полный доступ ко всем возможностям Telegram API, включая отправку и получение сообщений, управление клавиатурой, обработку медиафайлов (фотографий, видео, документов) и многое другое.
Вот несколько полезных источников, которые помогут разобраться в библиотеке и следить за её обновлениями:
- официальная документация,
- репозиторий библиотеки на GitHub,
- русскоязычный телеграм-чат, посвящённый Aiogram,
- англоязычный чат, посвящённый Aiogram,
- канал с новостями библиотеки,
- тестовый бот на основе Aiogram.
Создаём эхо-бота
Переходим к созданию телеграм-бота. Потренируемся на простом примере — создадим эхо-бота, который отвечает на сообщения пользователя его же словами.
Для этого нам необходимо:
- установить Python и настроить виртуальное окружение;
- зарегистрировать бота в специальном телеграм-канале @BotFather;
- установить библиотеку Aiogram;
- написать код эхо-бота, связав его по API с Telegram.
Шаг 1
Устанавливаем Python
На macOS или Linux. Python установлен в эти операционные системы изначально. Чтобы проверить его наличие, откройте терминал и введите команду:

На Windows требуется установка Python. Сделать это проще всего по нашей инструкции.
Шаг 2
Создаём виртуальное окружение
После установки и проверки Python требуется установить виртуальное окружение с помощью virtualenv. Это специальный инструмент, который позволяет изолировать друг от друга проекты в разработке, независимо устанавливая для них библиотеки и пакеты. Удобно, когда вы работаете над разными приложениями одновременно.
virtualenv устанавливается через терминал:

Открываем его, жмём кнопку «Запустить» и вводим команду /newbot:

Теперь напишем название и юзернейм для нашего бота. Назовём его echo_skillbox_bot (теперь это имя занято, так что вам надо будет придумать своё). В ответ придёт наш токен, который мы будем использовать для подключения к API Telegram.

Этот токен мы сохраняем — он потребуется нам в будущем.
Шаг 4
Подключаем Aiogram
Для установки Aiogram воспользуемся менеджером пакетов PIP. Вводим в терминал:

Находим нашего бота в Telegram по имени @echo_skillbox_bot и запускаем его, нажав на кнопку Начать. В ответ на это или на команду /start нам придёт приветственное сообщение:

Попробуем написать что-то:

Как мы видим — всё работает. Бот возвращает нам наши сообщения.
Что дальше?
Расширять функциональность бота, указывая для разных команд пользователя разные ответы. Например, добавить раздел помощи, который будет появляться по команде /help. Или настроить запуск кода на виртуальном сервере, чтобы бот работал независимо от вашего компьютера.
Во второй части статьи мы добавим к нашему боту кнопки и новые возможности. Так что обязательно заходите и читайте 🙂
Больше интересного про код в нашем телеграм-канале. Подписывайтесь!
Читайте также:
- С# для новичков: развеиваем мифы и пишем простого чат-бота
- «Я удивился, когда написал код, а он заработал»: как живёт начинающий Python-разработчик
- Учимся верстать: что такое CSS
Как разработать Telegram-бота на Python+C
Создаем Telegram-бот, работаем c I/O Bound нагрузкой, подключаем фичи Google Drive и пишем на C внутри Python.
Эта инструкция — часть курса «Как создавать Telegram-ботов».
Смотреть весь курс
Введение
В этой инструкции мы создадим приложение, которое интегрируется со сторонними API. Разберем I/O Bound нагрузку и поработаем с асинхронностью в Python.
Часть сервера Telegram-бота мы будем писать на С, так как он считается перформанс-ориентированным языком, поэтому посмотрим также модуль обработки СPU Bound нагрузки, использующийся для сложных вычислений процессора.
Мы постараемся объяснить все: от развертывания каждого модуля до настройки проекта на удаленном сервере.
Работать все это будет в вебе, поэтому для примера клиент-серверной архитектуры используется Telegram. У нас по дефолту будет мобильный+десктопный клиент, поэтому не придется писать фронтенд самому.
Теория
У многих приложений есть открытый интерфейс (API), к которому можно подключиться из своих программ. Например, так можно подключиться к YouTube и попросить сервис прислать описания всех роликов из конкретного плейлиста.

Что такое CPU Bound и I/O Bound нагрузка. Примеры
- Есть какая-то задача Х, которая сильно нагружает процессор. Например, одни из самых медленных операций, которые выполняет современный процессор — деление или тригонометрические функции типа sin или cos. Такие сложные для процессора операции называют CPU Bound нагрузкой.
- Если операции упираются в скорость сети, скорость записи или чтения какого-то файла с диска или ожидания запроса из базы данных — это I/O Bound нагрузка. Отправили запрос к серверу и ждете, пока он придет — процессор простаивает, как правило, 90% времени.

Как эти проблемы решаются в Python?
- Можно использовать модуль мультипроцессинга, который распараллелит вычисления на несколько процессов на нескольких реальных ядрах. Первый оверхед — создание новых процессов в операционной системе — достаточно дорогая операция. А их взаимодействие — второй оверхед, то есть обмен данными между ними — не самая простая вещь. Как минимум, чтобы передать объект между ними, объект должен быть сериализован и десериализован соответствующе. Если кратко: сериализация — это очень дорого.
- Мультитрединг — дешевле, особенно вскейле, то есть когда мы планируем масштабировать и создавать большие пулы потоков. Потому что поток — это более легковесная сущность с точки зрения операционной системы и в сравнении с процессом, как минимум.
Самое важное, что потоки расшаривают одну и ту же область памяти внутри одного процесса. То есть обмен данными между ними организован гораздо проще.
В Python есть GIL или блок интерпретатора, с которым могут быть проблемы. В каждый конкретный момент времени может работать только один, залоченный поток, а другим приходится ждать.

В случае решения второй проблемы и I/O Bound нагрузки, то в Python она решается асинхронным программированием.
Практика
О том, как создаются Telegram-боты, можно посмотреть здесь. Начнем с нашего интерфейса.
Возьмем первую библиотеку для Python: Python-telegram-bot.
Чтобы не захламлять компьютер чем-то лишним, будем использовать питоновскую виртуальную среду (venv). Это похоже на изолированный контейнер, в котором можно проводить любые опыты, и доступно это только внутри этой папки.
Сначала нам нужен токен от BotFather для аутентификации с API Telegram. Плюс здесь можно устанавливать ряд других настроек: изменить описание, поставить моды. BotFather по сути представляет собой админку.
Токен мы будем хранить в скрытых данных. За это в Python отвечает модуль dotenv, который также ставится внутри нашей виртуалки, и в сам файл уже переносим токен.
from bot.bot import start, list_files, upload_file, search_button, cython, pi_button, FIRST, SECOND from dotenv import load_dotenv from telegram.ext import ApplicationBuilder, CommandHandler, MessageHandler, filters, CallbackQueryHandler, ConversationHandler import os async def hello(update: Update, context: ContextTypes.DEFAULT_Type) ->Name: Name: await update.message.replay_text(f'Hello(update.effective_user.first_name)') load_dotenv() application = ApplicationBuilder().token(os.getenv('TOKEN')).build() app.add_handler(CommandHandler(“hello”, hello)) app.run.polling()Чтобы подружить Telegram с Google Drive воспользуемся API /quickstart/python. Здесь нам нужно получить что-то вроде токена, как и в случае аутентификации с API Telegram, только в Google API это называется(Credentials). В main.py мы должны добавить и токен, и обработчик функции start.
По этой логике нужно написать еще одну функцию и добавить ее в еще один handler уже для сэмпла от Google Drive.
Добавляем еще один CommandHandler (обработчик команд или все, что мы пишем через слэш).
В Telegram, проверить работу функции можно командой /files, которую мы можем легко добавить в админке BotFather
По коду Google: мы достаем секретки и вытаскиваем все файлы, дальше выводим их в консоль. Можно сделать, чтобы ответ приходил не в консоль сервера, а сразу в бот на клиент.
Загрузка файлов
Для демонстрации возможностей добавим еще и загрузку файлов. Google предлагает здесь обширный список того, что можно делать: Работа со всеми видами файлов, работа с UI Google Drive, и так далее.
Нам для интерактивности нужна динамика, поэтому мы будем грузить файл сначала на наш сервер через интерфейс Telegram, а уже оттуда его перенаправлять в Google Drive.
Чтобы загрузить файл сначала к нам на сервер, воспользуемся вот этим код-сниппетом из библиотеки telegram-bot-api а конкретно Download file.
Промежуточный итог

У нас есть сервер. Место, где мы разворачиваем наш код: удаленный или наш компьютер. Там мы запускаем наш Telegram-бот. Через интерфейс Telegram-бота, который нам дается по стандарту, мы можем загрузить файл, и он будет у нас лежать либо на компьютере, либо на удаленном сервере.
Этим сниппетом сначала загружаем файлы на компьютер. В отдельную функцию вынесем аутентификацию с Google Drive, чтобы было почище. Дальше во все захардкоденные сниппеты Google мы добавим наши динамические данные в виде имен файлов и путей. Все это достается из аргументов Update и Context.
Добавим также обработчик команд MessageHandler для медиасообщений.
Теперь, если вызвать эту функцию, то сначала файл появится в нашей папке Downloaded, а потом в Google Drive.
В конечном счете код в файле main.py у нас должен выглядеть так:
from bot.bot import start, list_files, upload_file, search_button, cython, pi_button, FIRST, SECOND from dotenv import load_dotenv from telegram.ext import ApplicationBuilder, CommandHandler, MessageHandler, filters, CallbackQueryHandler, ConversationHandler import os def main(): import cProfile import pstats load_dotenv() application = ApplicationBuilder().token(os.getenv('TOKEN')).build() application.add_handler(CommandHandler('start', start)) with cProfile.Profile() as pr: application.add_handler(CommandHandler('files', list_files)) stats = pstats.Stats(pr) stats.sort_stats(pstats.SortKey.TIME) stats.dump_stats(filename='profiling.prof') conv_handler = ConversationHandler( entry_points=[CommandHandler("cython", cython)], states=< FIRST: [CallbackQueryHandler(search_button)], SECOND: [CallbackQueryHandler(pi_button)], >, fallbacks=[CommandHandler("cython", cython)], ) application.add_handler(conv_handler) application.add_handler(MessageHandler(filters.Document.ALL, upload_file)) application.run_polling() if __name__ == '__main__': main()На этом мы закончили работу с I/O Bound нагрузкой и ее функциями. А что если мы хотим добавить функциональность по нагрузке процессора? Вариаций действительно много. Можно сделать промежуточный лэйер, который будет брать видео, которые мы грузим, монтажить их в клип, сжимать и выгружать на Google Drive уже готовые препродакшн видео.
Модуль для сложных вычислений на процессоре
Следующая фича на бэкенде. Мы сейчас добавим модуль для CPU Bound обработки именно на процессоре, и делать это будет Python в связке с языком С. Более того, делать мы это будем в нескольких потоках. То есть мы отключим питоновский GIL.
Потенциально это может создать много проблем с синхронизацией, но тема многопоточности — это отдельная история, которую мы в рамках инструкции вынесем за скобки.
Первый модуль будет большой аллокацией + бинарный поиск по массиву. Заодно так проверим, как это работает. Папка lowlevel для C-файлов и сразу заведем модуль с парой простых функций.
int*allocate(int N) int* ptr = malloc(sizeof(int)* N);Выделяем память для массива данных. Внутри функции поиска элементов будем вызывать наши функции для аллокации и генерации данных. Так можно увидеть наглядно, как работает процессор линейно, в нескольких потоках и в нескольких процессах. Эта практика сейчас используется для наглядности, но в боевых проектах так делать не стоит.
Чтобы вызывать код на С из-под Python нам потребуется Cython. Это суперсет Python, который позволяет писать на C прямо внутри Python. Чтобы все это работало, нужно поставить это в виртуалку и добавить файл для интерфейса Python и C.
cdef extern from "lowlevel/module.c" nogil; int binary_search_imp(int index) cdef int b_search_(int index):Затем создаем промежуточную функцию, которая будет вызывать нашу экспортированную C функцию.
cdef int b_search(int index): return binary_search_impl(index); def binary_search(int index, results = None): cdef int result; with nogil: result = b_search(index); if results is not None: results.append(result) return resultСамое важное — поставить аннотацию nogil, то есть здесь мы отключаем лок интерпретатора (GIL).
Итак, у нас есть С файл с core-функциями. У нас есть файл, чтобы этими функциями пользоваться в самом Python.
Теперь в новом файле опишем все Python-функции. Чтобы не путаться, назовем его, например, performance.py. В него сразу добавим функцию по вычислению дельты времени, от начала и конца исполнения. Она позволит наглядно увидеть скорость Python в линейном режиме, многопоточном и в многопроцессном.
Функция, внутри которой есть функция-обертчик, где есть вызов функции, которую мы декорируем. Чтобы протестить все, что мы написали, нам нужны три основных функции.
Линейная. Все вычисления будут происходить по очереди в одном потоке, как обычно работает Python. Асинхронность здесь не сработает, потому что все это нагружает именно процессор, а не любого рода ввод/вывод.
def linear(N) results = [] binary_search(N, results) return resultsМногопоточная. Создаем пять потоков по одному вызову в каждом.
def multithreaded(N): results = []Мультипроцессинговая. Функция с изолированным друг от друга процессами: shared.list.
shared.list. def multithreaded(N): results = mp.Managed.lists()Теперь перейдем к следующему конфигу, по настройке Cython. Назовем его setup.py. Что здесь должно быть?
- файл, который нужно скомпилировать,
- модуль, который мы создаем и будем использовать,
- функция setup, которая будет все это структурировать.
Код выглядит так:
from setuptools import setup from distutils.extension import Extension from Cython.Distutils import build_ext sourceFiles = ['bot/nogil.pyx'] ext_modules = [ Extension("nogil", sourceFiles ), ] setup( name='google drive app', cmdclass=, ext_modules=ext_modules )Добавим простой bash-script, чтобы собирать все было удобнее.
# !/bin/bash [! -e "nogil.c" ] || rm "nogil.c" find . -name "*.so" -type f -delete python3 setup.py build_ext -b build mv bot/*.c bot/lowlevel/Теперь все должно адекватно пересобираться и раскладываться по своим местам.
Чтобы в самом Telegram-боте это выглядело красиво, добавим пару кнопок. В Inline-боте нужны две функции: описание и кнопка + события, которые будут происходить, когда мы будем на нее нажимать.
Вызываем функцию бинарного поиска в наших 3-х тестах. Линейном, многопоточном и многопроцессном, дергаем наши три функции. Добавляем обработчики для кнопок и можно тестировать.
Почти секунда у нас уйдет, чтобы выделить необходимую память для каждого вызова функции. Многопоточность обгоняет линейную более чем на 50%. Многопроцессность также быстрее, но все же уступает многопоточности.
В конце у нас должен получиться примерно такой код в bot.py:
from __future__ import print_function import os import logging from telegram import Update from telegram.ext import ContextTypes import os.path from google.auth.transport.requests import Request from google.oauth2.credentials import Credentials from google_auth_oauthlib.flow import InstalledAppFlow from googleapiclient.discovery import build from googleapiclient.errors import HttpError from googleapiclient.http import MediaFileUpload from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update from bot.performance import linear, multithreaded, multiprocessed, linear_pi, mt_pi, mp_pi logging.basicConfig( format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO ) def creds(): # If modifying these scopes, delete the file token.json. SCOPES = ['https://www.googleapis.com/auth/drive'] """Shows basic usage of the Drive v3 API. Prints the names and ids of the first 10 files the user has access to. """ creds = None # The file token.json stores the user's access and refresh tokens, and is # created automatically when the authorization flow completes for the first # time. if os.path.exists('token.json'): creds = Credentials.from_authorized_user_file('token.json', SCOPES) # If there are no (valid) credentials available, let the user log in. if not creds or not creds.valid: if creds and creds.expired and creds.refresh_token: creds.refresh(Request()) else: flow = InstalledAppFlow.from_client_secrets_file( 'credentials.json', SCOPES) creds = flow.run_local_server(port=0) # Save the credentials for the next run with open('token.json', 'w') as token: token.write(creds.to_json()) return creds async def start(update: Update, context: ContextTypes.DEFAULT_TYPE): await context.bot.send_message(chat_id=update.effective_chat.id, text="I'm a bot, please talk to me!") async def list_files(update: Update, context: ContextTypes.DEFAULT_TYPE): try: service = build('drive', 'v3', credentials=creds()) # Call the Drive v3 API results = service.files().list( pageSize=10, fields="nextPageToken, files(id, name)").execute() items = results.get('files', []) if not items: print('No files found.') return #4.362e-06 # l = [] # for f in items: # l.append(f.get("name")) print('Files:') #4.103e-06 for item in items: await context.bot.send_message(chat_id=update.effective_chat.id, text=item.get("name")) print(u' ()'.format(item['name'], item['id'])) except HttpError as error: # TODO(developer) - Handle errors from drive API. print(f'An error occurred: ') async def upload_file(update: Update, context: ContextTypes.DEFAULT_TYPE): newFile = await update.message.effective_attachment.get_file() await newFile.download(custom_path="downloaded/" + update.message.effective_attachment.file_name) try: # create drive api client service = build('drive', 'v3', credentials=creds()) file_metadata = media = MediaFileUpload("downloaded/"+update.message.document.file_name, mimetype=update.message.document.mime_type, resumable=True) # pylint: disable=maybe-no-member file = service.files().create(body=file_metadata, media_body=media, fields='id').execute() print(F'File ID: ') except HttpError as error: await context.bot.send_message(chat_id=update.effective_chat.id, text="Failure") print(F'An error occurred: ') file = None await context.bot.send_message(chat_id=update.effective_chat.id, text="Success") return file.get('id') FIRST, SECOND = range(2) async def cython(update: Update, context: ContextTypes.DEFAULT_TYPE): keyboard = [ [InlineKeyboardButton(u"Search", callback_data=str(FIRST))] ] reply_markup = InlineKeyboardMarkup(keyboard) await update.message.reply_text(u"First module", reply_markup=reply_markup) return FIRST async def search_button(update: Update, context: ContextTypes.DEFAULT_TYPE): query = update.callback_query keyboard = [ [InlineKeyboardButton(u"Pi", callback_data=(SECOND))] ] index = ((2**31-1) // 32 - 1) await context.bot.send_message(chat_id=update.effective_chat.id, text="lin: "+linear(index)) await context.bot.send_message(chat_id=update.effective_chat.id, text="mt: "+multithreaded(index)) await context.bot.send_message(chat_id=update.effective_chat.id, text="mp: "+multiprocessed(index)) #reply_markup = InlineKeyboardMarkup(keyboard) await context.bot.edit_message_text(chat_id=query.message.chat_id, message_id=query.message.message_id, text=u"Second module") reply_markup = InlineKeyboardMarkup(keyboard) await context.bot.edit_message_reply_markup(chat_id=query.message.chat_id, message_id=query.message.message_id, reply_markup=reply_markup) return SECOND async def pi_button(update: Update, context: ContextTypes.DEFAULT_TYPE): query = update.callback_query await context.bot.send_message(chat_id=update.effective_chat.id, text="lin: "+linear_pi(100000000)) await context.bot.send_message(chat_id=update.effective_chat.id, text="mt: "+mt_pi(100000000)) await context.bot.send_message(chat_id=update.effective_chat.id, text="mp: "+mp_pi(100000000)) await context.bot.edit_message_text(chat_id=query.message.chat_id, message_id=query.message.message_id, text=u"You can add more CPU bound modules")Тестируем на Linux сервере
Мы будем использовать последнюю версию Ubuntu и выберем самое простое железо. Кроме этого, с помощью Shared Line можно использовать только 10% ресурсов, поэтому это будет еще дешевле, но под наш pet-проект хватит. В день такая конфигурация будет стоить не больше 15 рублей.

Поднимаем и обновляем там все, потому что это fresh Linux. Далее загружаем весь код бота на сервер, и можно приступать к тестированию: закинем пару файлов в наш Telegram-бот. Тестируем работу загрузки файлов. Вывода файлов, и сами функции по нагрузке CPU bound вычислений.
Заключение
Мы подключаемся к открытому API Google Drive, и вместо того, чтобы в самом диске нажимать разные кнопки, мы можем управлять им из-под Telegram-бота. Сделать это можно не только с диском, но и с любым сервисом, у которого есть открытый API.

Кроме этого, мы рассмотрели пару фич для работы с CPU Bound нагрузками, что может помочь разгрузить какие-то сложные для процессора вычисления.