Сообщить об ошибке.

Библиотека python-telegram-bot в Python.

Создание Telegram каналов и ботов.

Пакет python-telegram-bot предоставляет чистый интерфейс Python для Telegram Bot API. Он совместим с версиями Python 3.6+. Пакет python-telegram-bot также может работать с PyPy3 (официально не поддерживается), хотя раньше было много проблем.

В дополнение к чистой реализации API эта библиотека содержит ряд высокоуровневых классов, которые делают разработку ботов простой и понятной. Эти классы содержатся в модуле telegram.ext.

Реализация чистого API без расширений telegram.ext доступна как отдельный пакет python-telegram-bot-raw.

Установка пакета python-telegram-bot в виртуальное окружение:

# создаем виртуальное окружение, если нет
$ python3 -m venv .telegram --prompt TelegramBot
# активируем виртуальное окружение 
$ source .telegram/bin/activate
# ставим модуль python-telegram-bot
(TelegramBot):~$ python -m pip install -U python-telegram-bot
# или установка чистого API без расширений
(TelegramBot):~$ python -m pip install -U python-telegram-bot-raw

Пакет python-telegram-bot в основном будет разбираться на примерах. Содержание, обзорного/вводного материала по библиотеке ниже. Меню с материалами по всему разделу - справа. Поехали...

Содержание:


Чистая реализация официального Telegram Bot API.

API бота предоставляется через класс telegram.Bot. Методы, определенные в telegram.Bot являются эквивалентами в виде методов snake_case, описанных в официальной документации Telegram Bot API. Для удобства, также доступны точные названия методов в виде camelCase, указанные в документации Telegram. Так, например, вызов telegram.Bot.send_message совпадает с вызовом метода telegram.Bot.sendMessage.

Все классы объектов Telegram Bot API расположены в основном модуле пакета telegram, например, класс объекта Message доступен как telegram.Message.

Чтобы сгенерировать токен доступа, необходимо пообщаться с @BotFather и выполнить несколько простых шагов, описанных в разделе Команды и оповещения @BotFather в Telegram.

Чтобы получить представление об API и о том, как его использовать с пакетом python-telegram-bot, запустите интерпретатор Python и выполните следующие несколько шагов.

Сначала создаем экземпляр telegram.Bot. Константу TOKEN следует заменить токеном API, который был получен от @BotFather:

>>> import telegram
>>> TOKEN = 'Замените эту строку на token, полученный от @BotFather'
>>> bot = telegram.Bot(token=TOKEN)
# Чтобы проверить правильность учетных данных, вызываем метод bot.getMe():
>>> print(bot.get_me())
# {"first_name": "Toledo's Palace Bot", "username": "ToledosPalaceBot"}

Примечание: боты не могут инициировать чаты с пользователями. Пользователь должен либо добавить их в группу, либо сначала отправить им сообщение. Для подключения к создаваемому боту или каналу в основном используются ссылки, такие как https://telegram.me/bot_username или можно попробовать найти бота по имени в своем десктопном или мобильном приложении.

Как отвечать/получать сообщения на чистом API?

Для получения сообщений, отправленных боту, можно использовать метод API .getUpdates.

Примечание: не нужно использовать .get_updates, если пишете своего бота с модулем расширения telegram.ext, поскольку telegram.ext.Updater получает все обновления/сообщения в автоматическом режиме.

На чистом API это выглядит следующим образом:

updates = bot.get_updates()
print([upd.message.text for upd in updates])

Получение изображения, отправленного боту:

updates = bot.get_updates()
print([upd.message.photo for u in updates if upd.message.photo])

Для отправки сообщения всегда нужен будет chat_id:

chat_id = bot.get_updates()[-1].message.chat_id
bot.send_message(chat_id=chat_id, text="I'm a bot, please talk to me!")

Примечание. Метод .send_message, как и любой из методов send_* класса Bot возвращает экземпляр класса Message, поэтому его можно использовать в коде позже.

Ответ на конкретное сообщение, полученное в обновлении:

update.message.reply_text("I'm sorry Dave I'm afraid I can't do that.")

Примечание. Существуют эквиваленты этого метода для ответа с фотографиями, аудио и т. д., а так же аналогичные эквиваленты встречаются по всей библиотеке python-telegram-bot.

Пример бота для ответа на сообщения на чистой реализации API Telegram.

Простой бот для ответа на сообщения Telegram. Пример построен на чистом API Telegram, который реализует пакет python-telegram-bot.

import logging
from time import sleep

import telegram
from telegram.error import NetworkError, Unauthorized

UPDATE_ID = None

def echo(bot):
    """Эхо сообщение отправленное пользователем."""
    global UPDATE_ID
    # Запрашиваем обновление после последнего update_id
    for update in bot.get_updates(offset=UPDATE_ID, timeout=10):
        UPDATE_ID = update.update_id + 1

        # бот может получать обновления без сообщений
        if update.message:
            # не все сообщения содержат текст
            if update.message.text:
                # Ответ на сообщение
                update.message.reply_text(f'ECHO: {update.message.text}')


if __name__ == '__main__':
    """Запускаем бота."""
    global UPDATE_ID
    # Токен авторизации бота Telegram
    bot = telegram.Bot('TOKEN')

    # получаем первый ожидающий `update_id`
    try:
        UPDATE_ID = bot.get_updates()[0].update_id
    except IndexError:
        UPDATE_ID = None

    logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')

    while True:
        try:
            echo(bot)
        except NetworkError:
            sleep(1)
        except Unauthorized:
            # Пользователь удалил или заблокировал бота.
            UPDATE_ID += 1

Модуль расширения telegram.ext.

Модуль расширений telegram.ext построен поверх чистой реализации Telegram Bot API. Он предоставляет простой в использовании интерфейс и снимает с программиста некоторую работу.

Он состоит из нескольких классов, но два наиболее важных - это 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 версии. Это обеспечивает лучшую обратную совместимость со старыми версиями и дает пользователям время для обновления. Начиная с 13-ой версии, значение аргумента 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!")

Цель состоит в том, чтобы эта функция вызывалась каждый раз, когда бот получает сообщение с серверов 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 сможет просмотреть сообщение. После обработки сообщения, все дальнейшие обработчики игнорируются. Чтобы обойти это, можно передать группу целочисленных ключевых аргументов в метод Dispatcher.add_handler со значением, отличным от 0.

Остановить бота можно командой 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()