Телеграм боты и python

Введение

Складывается ощущение, что telegram боты уже созданы для всех случаев жизни, но как оказывается желающих реализовать telegram бота для своих идей меньше все-равно не становится. Написание telegram ботов навык, который может пригодиться вам в самый неожиданный момент, поэтому время потраченное на изучение python библиотек связанных с этим точно не будет потрачено впустую.

BotFather

Telegram предоставляет API для работы с их ботами и начать знакомство с ним стоит с BotFather, его можно найти просто через поиск в самом telegram и сразу приступить к ознакомлению с документацией и возможностями.

Для создания бота нужно обратиться к BotFather в Telegram. По команде /start вы получите список команд для работы с ботами. Команда /newbot (название) - создаст нового бота и BotFather выдаст вам token вашего нового бота.

Первым сообщением создадим бота, вторым дадим название, третьим придумаем url бота и в итоге получим токен для связи с нашим ботом.

Библиотека telebot и первый бот

Telebot не единственная библиотека для работы с телеграм ботами, но начать знакомство хотелось бы с нее. Установка telebot:
pip install pyTelegramBotAPI
Сразу напишем самого простого бота и разберем его.

firstbot.py
import telebot

token = '5211862289:AAHG4VjexC7ZZ_rueMosmbX3lydGcM-5BHY'

bot = telebot.TeleBot(token)


@bot.message_handler(commands=["start"])
def start_message(message):
    bot.send_message(message.chat.id, text="Меня зовут learn_bot")


@bot.message_handler(content_types=['text'])
def return_text(message):
    bot.send_message(message.chat.id, 'Зачем вы написали? - ' + '\"' + message.text + '\"')

 

bot.polling()

Импортируем telebot. Создадим переменную token куда поместим token, полученный от BotFather. Создадим экземпляр бота и передадим туда наш токен. Теперь напишем две функции, первая start_message будет отправлять приветственное сообщение на команду start, а вторая return_message будет повторять наше сообщение. Обе функции будут принимать message и отправлять сообщение методом .send_message, внутри этого метода первым параметром мы свяжем сообщение с чатом нашего бота, а вторым вернем что-нибудь пользователю. Обе функции обернем в декоратор @bot.message_handler благодаря ему бот реагирует на входящие сообщения. В декоратор первой функции передадим атрибут типа commands, внутри которого можно передать команду или список команд, при отправке которых мы будем получать какое-то, например, приветственное сообщение. В декоратор второй функции передадим content_types и в нашем случае скажем, что тип передаваемого в бот контента - это текст. Помимо текст можно передать, например, аудио или видео, content_types=['audio'] и content_types=['video'] соответственно. Для работы бота после всего кода напишем bot.polling(), без этой команды программа отработает и завершится, с ней программа будет работать, пока не прервешь ее работу самостоятельно.
Посмотрим, что у нас вышло.

Все работает, бот отправляет приветственное сообщение в ответ на команду /start и повторяет любое наше сообщение. Достаточно просто.

Кнопки для телеграм бота

Разделяют такие типы как: ReplyKeyboardMarkup и InlineKeyboardMarkup.
Для начала разберемся с ReplyKeyboardMarkup, данный тип используется для общения с пользователем по шаблонному сценарию. Сразу обсудим на примере.

firstbot.py
import telebot
from telebot import types

token = '5211862289:AAHG4VjexC7ZZ_rueMosmbX3lydGcM-5BHY'

bot = telebot.TeleBot(token)


@bot.message_handler(commands=["start"])
def start_message(message):
    buttons = types.ReplyKeyboardMarkup(resize_keyboard=True)
    button1 = types.KeyboardButton('Нет')
    button2 = types.KeyboardButton('Далее')
    buttons.add(button1, button2)
    bot.send_message(message.chat.id, text="Меня зовут learn_bot", reply_markup=buttons)


@bot.message_handler(content_types=['text'])
def answer(message):
    try:
        if message.text == 'Нет':
            bot.send_message(message.chat.id, text='...')
        elif message.text == 'Далее':
            buttons = types.ReplyKeyboardMarkup(resize_keyboard=True)
            btn1 = types.KeyboardButton('Что делать?', request_contact=True)
            btn2 = types.KeyboardButton('Это все?', request_location=True)
            buttons.add(btn1, btn2)
            bot.send_message(message.chat.id, text='Выбирай кнопку', reply_markup=buttons)

        elif message.text == 'Что делать?':
            bot.send_message(message.chat.id, text='Не знаю')

        elif message.text == 'Это все?':
            bot.send_message(message.chat.id, text='все')
    except Exception as e:
        print(repr(e))


bot.polling(none_stop=True)

Для работы с кнопками нам понадобится модуль types из telebot, импортируем его. Кнопки из ReplyKeyboardMarkup также оборачиваются в декоратор @message_handler. Функцией start_message будем вызывать первый набор кнопок. В первую очередь нужно создать переменную типа ReplyKeyboardMarkup, в данном случае назовем ее buttons, можно сказать эта переменная - хранилище наших кнопок. Атрибут resize_keyboard со значением True уменьшит высоту кнопок до максимально возможной. Далее создаем столько кнопок, сколько нам нужно. Делается это типом .KeyboardButton, куда передается текст кнопки, помимо текста кнопки в нее можно передать параметры request_contact и request_location, в значениях True нажатие на эту кнопку вернет данные об аккаунте (номер телефона, аватарку и кнопку добавить в друзья), из которого была нажата кнопка и геолокацию, соответственно. При нажатии на кнопки с активированными параметрами request_contact или request_location появится модальное окно, которое попросит ваше подтверждение на отправку ваших данных в этот чат. Далее добавляем методом .add эти кнопки в наше хранилище кнопок, которое потом передается в параметр reply_markup. В итоге работает вся эта функция так: после отправки команды /start мы видим содержание переменной 'text=' из метода .send_message и перед нами появляется список кнопок из параметра 'reply_markup='. Вторая функция содержит ответы на эти кнопки. Я решил обернуть эту функцию в блок try except, это может быть очень полезно, когда вы осваиваете новые библиотеки, рекомендую не забывать про обработку ошибок. Что касается тела второй функции, то в нем нет ничего нового, проверяем на какую кнопку нажал пользователь, с помощью if и elif. При нажатии на первую будем получать ответ, а при нажатии на вторую помимо ответа обновим список кнопок. И сразу же в этой функции пропишем ответы в случае нажатия уже на новые кнопки. Посмотрим на результат.

Все работает так, как и было задумано: после отправки /start видим первый набор кнопок, после нажатия на 'далее' набор кнопок обновляется. Еще один момент связанный с передачей телефона и геолокации. Если, например, в кнопке есть текст, а рядом с ним параметр request_contact=True, то в таком случае при нажатии на кнопку мы будем получать только карточку с данными пользователя, но если просто написать текст этой кнопки в чат, то мы уже не получим карточку с данными пользователя, а получим ответ, который был задан для этой кнопки. Вот пример для наглядности.

request_contact.py
import telebot
from telebot import types

token = '5211862289:AAHG4VjexC7ZZ_rueMosmbX3lydGcM-5BHY'

bot = telebot.TeleBot(token)


@bot.message_handler(commands=['start'])
def start_message(message):
    buttons = types.ReplyKeyboardMarkup(resize_keyboard=True)
    buttons1 = types.KeyboardButton('Это я', request_contact=True)
    buttons.add(buttons1)
    bot.send_message(message.chat.id, text="Меня зовут learn_bot", reply_markup=buttons)


@bot.message_handler(content_types=["text"])
def answer(message):
    if message.text == 'Это я':
        bot.send_message(message.chat.id, text='И что?')


bot.polling()

При нажатии на кнопку отправляется информация о нас, при написании текста кнопки вручную, отправляется текстовый ответ.

Перейдем теперь к InlineKeyboardMarkup.

Но перед этим покажу как установить приветственное сообщение для бота.

Команда /setdescriprion в BotFather позволяет это сделать. Теперь напишем программу, благодаря которой наглядно увидим разницу между ReplyKeyboardMarkup и InlineKeyboardMarkup.

request_contact.py
import telebot
from telebot import types

token = '5211862289:AAHG4VjexC7ZZ_rueMosmbX3lydGcM-5BHY'

bot = telebot.TeleBot(token)


@bot.message_handler(commands=['start'])
def start_message(message):
    start_button = types.ReplyKeyboardMarkup(resize_keyboard=True)
    start_button.add(types.KeyboardButton(text='начать'))
    bot.send_message(message.chat.id, text='Напиши начать', reply_markup=start_button)


@bot.message_handler(content_types=["text"])
def next_message(message):
    if message.text.lower() == 'начать':
        buttons = types.InlineKeyboardMarkup(row_width=2)
        button1 = types.InlineKeyboardButton('Можно выбрать это', callback_data='but1')
        button2 = types.InlineKeyboardButton('Или это', callback_data='but2')
        button3 = types.InlineKeyboardButton('Еще это', callback_data='but3')
        buttons.add(button1, button2, button3)
        bot.send_message(message.chat.id, text='Вот: ', reply_markup=buttons)
    else:
        bot.send_message(message.chat.id, text='Я что просил?')
bot.polling()

В первой функции ничего нового. Во второй создадим хранилище типа InlineKeyboardMarkup, вместо resize_keyboard этот тип принимает атрибут row_width, который говорит сколько кнопок будет в одном ряду. По умолчанию этот параметр равен 3 и указывать его необязательно. Далее добавим 3 кнопки типа InlineKeyboardButton, у этого типа кнопок есть параметр callback_data - это, можно сказать, id кнопки, по которому можно к ней обратиться. Обращаемся мы к этим кнопкам и взаимодействуем с ними внутри декоратора - callback_query_handler, об этом чуть ниже. И выглядит наш бот таким образом.

Добавили приветственное сообщение, после команды /start появляется кнопка 'начать' типа KeyboardButton, при нажатии на которую увидим сообщение с расположенными под ним кнопками типа InlineKeyboardMarkup. Благодаря row_width=2 первые две кнопки расположены в строку, а третья находится под ними. Пока эти кнопки ничего не делают. Сейчас это исправим.

request_contact.py
import telebot
from telebot import types

token = '5211862289:AAHG4VjexC7ZZ_rueMosmbX3lydGcM-5BHY'

bot = telebot.TeleBot(token)


@bot.message_handler(commands=['start'])
def start_message(message):
    start_button = types.ReplyKeyboardMarkup(resize_keyboard=True)
    start_button.add(types.KeyboardButton(text='начать'))
    bot.send_message(message.chat.id, text='Напиши начать', reply_markup=start_button)


@bot.message_handler(content_types=["text"])
def next_message(message):
    if message.text.lower() == 'начать':
        buttons = types.InlineKeyboardMarkup(row_width=2)
        button1 = types.InlineKeyboardButton('Можно выбрать это', callback_data='but1')
        button2 = types.InlineKeyboardButton('Или это', callback_data='but2')
        button3 = types.InlineKeyboardButton('Еще это', callback_data='but3')
        buttons.add(button1, button2, button3)
        bot.send_message(message.chat.id, text='Вот: ', reply_markup=buttons)
    else:
        bot.send_message(message.chat.id, text='Я что просил?')


@bot.callback_query_handler(func=lambda call: True)
def callback(call):
    if call.data == 'but1':
        new_menu = types.InlineKeyboardMarkup()
        new_menu.add(types.InlineKeyboardButton('Ничего нового', callback_data='but4'))
        bot.edit_message_text('Новое меню, кнопка 1', call.message.chat.id, call.message.message_id,
                              reply_markup=new_menu)
    elif call.data == 'but2':
        new_menu_2 = types.InlineKeyboardMarkup()
        new_menu_2.add(types.InlineKeyboardButton('И тут', callback_data='but5'))
        bot.edit_message_text('Новое меню, кнопка 2', call.message.chat.id, call.message.message_id,
                              reply_markup=new_menu_2)
    elif call.data == 'but3':
        new_menu_3 = types.InlineKeyboardMarkup()
        new_menu_3.add(types.InlineKeyboardButton('И тут', callback_data='but6'))
        bot.edit_message_text('Новое меню, кнопка 3', call.message.chat.id, call.message.message_id,
                              reply_markup=new_menu_2)


bot.polling()

Кнопки типа InlineKeyboardButton оживляются внутри декоратора @bot.callback_query_handler, куда передается анонимная функция, возвращающая True, имя call можно заменить на любое другое и передать его внутрь функции. Имя call общепринятое название для этой переменной. Методом .data обратимся к параметру callback_data наших кнопок метод .edit_message_text вернет нам новое меню с кнопками. Если внутрь этого метода не передать параметры call.message.chat.id и call.message.message_id, то вместо появления нового меню на месте старого оно появится под этим меню.

Теперь наши кнопки активны и при нажатии на них на месте старого меню появляется новое, не хватает кнопки вернуться назад. Давайте добавим.

request_contact.py
import telebot
from telebot import types

token = '5211862289:AAHG4VjexC7ZZ_rueMosmbX3lydGcM-5BHY'

bot = telebot.TeleBot(token)


@bot.message_handler(commands=['start'])
def start_message(message):
    start_button = types.ReplyKeyboardMarkup(resize_keyboard=True)
    start_button.add(types.KeyboardButton(text='начать'))
    bot.send_message(message.chat.id, text='Напиши начать', reply_markup=start_button)


@bot.message_handler(content_types=["text"])
def next_message(message):
    if message.text.lower() == 'начать':
        buttons = types.InlineKeyboardMarkup(row_width=2)
        button1 = types.InlineKeyboardButton('Можно выбрать это', callback_data='but1')
        button2 = types.InlineKeyboardButton('Или это', callback_data='but2')
        buttons.add(button1, button2)
        bot.send_message(message.chat.id, text='Вот: ', reply_markup=buttons)
    else:
        bot.send_message(message.chat.id, text='Я что просил?')


@bot.callback_query_handler(func=lambda call: True)
def callback(call):
    if call.data == 'start':
        buttons = types.InlineKeyboardMarkup(row_width=2)
        buttons.add(types.InlineKeyboardButton('Можно выбрать это', callback_data='but1'),
                    types.InlineKeyboardButton('Или это', callback_data='but2'))
        bot.edit_message_reply_markup(call.message.chat.id, call.message.message_id, reply_markup=buttons)
    elif call.data == 'but1':
        new_menu = types.InlineKeyboardMarkup()
        new_menu.add(types.InlineKeyboardButton('Ничего нового', callback_data='but4'),
                     types.InlineKeyboardButton('назад', callback_data='start'))
        bot.edit_message_text('Новое меню, кнопка 1', call.message.chat.id, call.message.message_id,
                              reply_markup=new_menu)
    elif call.data == 'but2':
        new_menu_2 = types.InlineKeyboardMarkup()
        new_menu_2.add(types.InlineKeyboardButton('И тут', callback_data='but5'),
                       types.InlineKeyboardButton('назад', callback_data='start'))
        bot.edit_message_text('Новое меню, кнопка 2', call.message.chat.id, call.message.message_id,
                              reply_markup=new_menu_2)


bot.polling()

Можно сделать это так. Удалим третью кнопку для компактности. Добавим по кнопке назад в новое меню первой и второй кнопки, с callback_data='start'. При обращении к этому значению будем передавать в это меню кнопки 'but1' и 'but2'. И методом .edit_message_reply_markup передадим эти кнопки в меню не изменив название самого меню.

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

У InlineKeyboardMarkup есть еще несколько типов кнопок, о которых было бы полезно знать.

InlineKeyboardMarkupbtns.py
import telebot
from telebot import types

token = '5211862289:AAHG4VjexC7ZZ_rueMosmbX3lydGcM-5BHY'

bot = telebot.TeleBot(token)


@bot.message_handler(commands=['buttons'])
def other_buttons(message):
    buttons = types.InlineKeyboardMarkup()
    url_button = types.InlineKeyboardButton(text='Github', url='https://github.com/')
    switch_button = types.InlineKeyboardButton(text='OtherChat', switch_inline_query='ForLearn')
    switch_button_here = types.InlineKeyboardButton(text='OtherBot_2', switch_inline_query_current_chat='')
    buttons.add(url_button, switch_button, switch_button_here)
    bot.send_message(message.chat.id, text='Типы кнопок', reply_markup=buttons)


bot.polling()

URL и Switch кнопки. С URL все понятно, указываем в параметре url='' ссылку на какой-нибудь ресурс и при нажатии на такую кнопку появится окно с предложением перейти по ссылке и при согласии пользователь перейдет по этой ссылке. Со Switch тоже ничего сложного, при нажатии на кнопку с параметром switch_inline_query нам предложат выбрать любой из наших telegram чатов и написать в него сообщение с тегом бота, из которого была нажата эта кнопка. Этот параметр можно оставить пустым, тогда в начале сообщения просто появится тег нашего бота, если в этом параметре есть какой-нибудь текст, то он также появится после тега бота. С switch_inline_query_current_chat ситуация аналогичная, только сообщение отправляется внутрь чата из, которого была нажата кнопка. Посмотрим как это выглядит.

Вот так выглядят эти кнопки, при нажатии на кнопку GitHub будет предложено перейти на github.

При нажатии на кнопку OtherBot_2 в чате появится тег бота и возможность писать сообщение.

При нажатии на кнопку OtherChat сначала будет предложено выбрать чат, я выберу BotFather и откроется чат с BotFather где также в начале сообщения проставится тег бота, из которого была нажата кнопка, а также поскольку в параметре switch_inline_query был написан текст он автоматически добавится после тега бота.


Материал будет дополняться...
(скорее всего)

Для отправки комментария необходимо авторизоваться



Комментарии

Здесь пока ничего нет...