PyTelegramBotAPI на примере проекта сбора обратной связи #3. Клавиатура
Эта статья предназначена для новичков. Я намерено опускаю сложные детали и нюансы, чтобы материал воспринимался легче.
Всем вновь привет! Сегодня мы продолжим цикл статей, посвящённых разработке телеграм бота на PyTelegramBotAPI.
В этом уроке я буду чередовать слова: пользователь и клиент. В контексте нашей задачи эти слова означают одно и тоже — пользователя нашего бота.
<<< Предыдущий урок
В этом уроке мы разберём клавиатуру, как одну из важнейших частей бота.
Но перед этим, вспомним что мы сделали ранее.
В первом уроке мы создали токен бота и написали простую функцию приветствия пользователя. Во втором уроке мы написали словарь для хранения пользовательских данных (имя, фамилия) и написали функции для запроса этих данных.
Знания освежили, теперь займёмся кодом. Прежде чем разбирать клавиатуру, немного доработает наш код. А именно: создадим новую функцию, которая будет запрашивать у пользователя имя. Конечно, вы можете сказать что такая функция уже есть это — welcome. Да, согласен, она запрашивает имя, но всё же эта функция запуска нашего бота, функция приветствия. Поэтому логику запроса данных лучше вынести.
@bot.message_handler(func=lambda message: message.text == 'Написать в поддержку')
def write_to_support(message):
chat_id = message.chat.id
bot.send_message(chat_id, 'Введите своё имя')
users[chat_id] = {}
bot.register_next_step_handler(message, save_username)
Эта функция и будет запрашивать у пользователя имя. Но, вас могла напрячь первая строка кода — строка с лямдой (прочтите про неё, лямбда нам ещё не раз пригодится). На самом деле ничего страшного тут нет. Так как фраза «Написать в поддержку«не является командой (команды со слеша — /), то и использовать аргумент commands
мы не можем. Тут то нам на помощь и приходит func, этот аргумент позволяет определить функцию, которая будет вызываться для проверки. Она (функция) должна `сообщить` библиотеке PyTelegramBotAPI, вызывать эту функцию на текущее сообщение или нет. Наша функция (лямбда) будет возвращать True
, только если текст сообщения будет «Написать в поддержку», на другой текст эта функция реагировать не будет. Надеюсь это понятно, если нет — прошу в комментарии, стараюсь отвечать всем.
Теперь поменяем наш welcome
. Так как я бы хотел сделать welcome просто функцией приветствия, то уберём весь код отвечающий за запрос имени.
Код обновлённого welcome будет выглядеть так:
@bot.message_handler(commands=['start'])
def welcome(message):
chat_id = message.chat.id
bot.send_message(chat_id,
'Добро пожаловать в бота сбора обратной связи')
Подготовительная часть завершена, теперь разберёмся с клавиатурой. В начале совсем немного теории из разряда: А зачем это нужно?
Если спросить любого человека, что ему проще — написать команду, ввести текст или кликнуть на одну кнопку, то большинство людей выберет последнее. И действительно, клик это самая быстрая и простая операция, а мы бы, конечно, хотели чтобы пользователи нашего бота были всем довольны. Чтобы бот не вызывал у них раздражение из-за излишней сложности и замудрённости.
Для этого и придумали клавиатура, выглядит она так:
Первый тип клавиатуру
Или так:
Второй тип клавиатуру
Изображения взяты с интернета.
Как вы можете заметить — это две абсолютно разные клавиатуры. Первая находится под строкой ввода сообщения (Reply), а вторая в самом сообщении (Inline).
Напишем клавиатуру Reply (та, которая находится под строкой ввода сообщения. Скриншот 1).
@bot.message_handler(commands=['start'])
def welcome(message):
chat_id = message.chat.id
keyboard = telebot.types.ReplyKeyboardMarkup(resize_keyboard=True)
button_support = telebot.types.KeyboardButton(text="Написать в поддержку")
keyboard.add(button_support)
bot.send_message(chat_id,
'Добро пожаловать в бота сбора обратной связи',
reply_markup=keyboard)
Теперь разберём код. На 4 строке я создал объект keyboard, это объект класса ReplyKeyboardMarkup
, который и является той самой Reply клавиатурой, в скобках указано resize_keyboard=True
, это сделано для нормальной работы кнопок, в некоторых случаях кнопки могут сильно растягиваться, расширяться и будут выглядеть крайне не презентабельно, resize_keyboard
решает эту проблему.
Далее создается кнопка button_support
, которая в аргументах содержит текст, отображаемый на кнопке.
В конце кода я добавляю кнопку на клавиатуру (делать это обязательно, или кнопка не отобразиться) и отправляю её в сообщении, указав параметр reply_markup
.
Вот так выглядит и работает клавиатуры:
Reply клавиатура с одной кнопкой
Поиграемся с кнопками, создадим, например, четыре штуки и будем их по разному размещать.
Кнопки в одной строке:
Кнопки в одном keyboard.add ()
Код:
@bot.message_handler(commands=['start'])
def welcome(message):
chat_id = message.chat.id
keyboard = telebot.types.ReplyKeyboardMarkup(resize_keyboard=True)
button_support = telebot.types.KeyboardButton(text="Написать в поддержку")
button1 = telebot.types.KeyboardButton(text="Кнопка 1")
button2 = telebot.types.KeyboardButton(text="Кнопка 2")
button3 = telebot.types.KeyboardButton(text="Кнопка 3")
keyboard.add(button_support, button1, button2, button3)
bot.send_message(chat_id,
'Добро пожаловать в бота сбора обратной связи',
reply_markup=keyboard)
Почему я сказал, что кнопки в одной строке, хотя последняя кнопка и находится на новой? Потому что в коде я их добавил на 1 строку, но так как телеграм умный, то он сам их раскидал по строкам чтобы они нормально отображались.
Теперь по две кнопки:
Кнопки в двух keyboard.add ()
Код:
keyboard = telebot.types.ReplyKeyboardMarkup(resize_keyboard=True)
button_support = telebot.types.KeyboardButton(text="Написать в поддержку")
button1 = telebot.types.KeyboardButton(text="Кнопка 1")
button2 = telebot.types.KeyboardButton(text="Кнопка 2")
button3 = telebot.types.KeyboardButton(text="Кнопка 3")
keyboard.add(button_support, button1)
keyboard.add(button2, button3)
Приведена только середина функции, так как начало и конец не изменились.
Как можно заметить, чтобы располагать кнопки на разных строках, достаточно их добавить в разные keyboard.add (), думаю это вы поняли.
Кстати, чтобы убрать Reply клавиатуру, надо использовать класс ReplyKeyboardRemove. Пример:
@bot.message_handler(commands=['remove_keyboard'])
def remove_keyboard(message):
chat_id = message.chat.id
keyboard = telebot.types.ReplyKeyboardRemove()
bot.send_message(chat_id,
'Удаляю клавиатуру',
reply_markup=keyboard)
Отлично, это была Reply клавиатура, а теперь поговорим про Inline. Напомню, что при этом типе клавиатуры кнопки будут добавлены не под строчку написания сообщения, а под само сообщение.
Для использования клавиатуры Inline, надо будет использовать класс клавиатуры — InlineKeyboardMarkup, а для кнопок — InlineKeyboardButton. Также в кнопках надо прописать параметр callback_data, зачем он нужен я вам расскажу чуть позже.
Изменим классы и получим такой код:
keyboard = telebot.types.InlineKeyboardMarkup()
button_save = telebot.types.InlineKeyboardButton(text="Сохранить",
callback_data='save_data')
button_change = telebot.types.InlineKeyboardButton(text="Изменить",
callback_data='change_data')
keyboard.add(button_save, button_change)
Запустим, проверим, и правда — кнопки есть. Но есть один минус. Они не работают. Дело в том, что Inline кнопки работают не так, как Reply. При нажатии никакого сообщения не отправляется, а сервер телеграма отправляет запрос к нашему боту, что была нажата кнопка. Появляется вопрос:, а как наш бот узнает какая кнопка нажата? Для этого мы и прописывали callback_data, при нажатии бот получит текст, который был указан в callback_data и по этому тексту мы и сможем понять, какая кнопка была нажата.
Добавим обработчик для двух наших кнопок (кнопки сохранения и изменения данных. Под данными я подразумевая имя и фамилию).
@bot.callback_query_handler(func=lambda call: call.data == 'save_data')
def save_btn(call):
message = call.message
chat_id = message.chat.id
bot.send_message(chat_id, f'Данные сохранены')
@bot.callback_query_handler(func=lambda call: call.data == 'change_data')
def save_btn(call):
message = call.message
chat_id = message.chat.id
bot.send_message(chat_id, f'Изменение данных.')
write_to_support(message)
Тут схожий принцип, что был и ранее. Мы используем лямбду для фильтрации. У нас две функции, реагирующие на разные кнопки, поэтому в первом декораторе мы ожидаем save_data, а во второй change_data.
Теперь разберёмся что же такое call. Как можно заметить, в функциях используется не message, а call. call — это объект, расширяющий стандартный message. Из полезного — в call есть call.data, это название кнопки на которую мы нажали (callback_data). Мы также можем получить и привычный нам message, для этого напишем:
message = call.message
Запустим и протестируем код:
Как можно заметить, всё прекрасно работает.
Получившийся код:
import telebot
from config import TOKEN
bot = telebot.TeleBot(TOKEN)
users = {}
@bot.message_handler(commands=['start'])
def welcome(message):
chat_id = message.chat.id
keyboard = telebot.types.ReplyKeyboardMarkup()
button_save = telebot.types.InlineKeyboardButton(
text="Написать в поддержку")
keyboard.add(button_save)
bot.send_message(chat_id,
'Добро пожаловать в бота сбора обратной связи',
reply_markup=keyboard)
@bot.message_handler(
func=lambda message: message.text == 'Написать в поддержку')
def write_to_support(message):
chat_id = message.chat.id
bot.send_message(chat_id, 'Введите своё имя')
users[chat_id] = {}
bot.register_next_step_handler(message, save_username)
def save_username(message):
chat_id = message.chat.id
name = message.text
users[chat_id]['name'] = name
bot.send_message(chat_id, f'Отлично, {name}. Теперь укажи свою фамилию')
bot.register_next_step_handler(message, save_surname)
def save_surname(message):
chat_id = message.chat.id
surname = message.text
users[chat_id]['surname'] = surname
keyboard = telebot.types.InlineKeyboardMarkup()
button_save = telebot.types.InlineKeyboardButton(text="Сохранить",
callback_data='save_data')
button_change = telebot.types.InlineKeyboardButton(text="Изменить",
callback_data='change_data')
keyboard.add(button_save, button_change)
bot.send_message(chat_id, f'Сохранить данные?', reply_markup=keyboard)
@bot.message_handler(commands=['who_i'])
def who_i(message):
chat_id = message.chat.id
name = users[chat_id]['name']
surname = users[chat_id]['surname']
bot.send_message(chat_id, f'Вы: {name} {surname}')
@bot.callback_query_handler(func=lambda call: call.data == 'save_data')
def save_btn(call):
message = call.message
chat_id = message.chat.id
bot.send_message(chat_id, f'Данные сохранены')
@bot.callback_query_handler(func=lambda call: call.data == 'change_data')
def save_btn(call):
message = call.message
chat_id = message.chat.id
bot.send_message(chat_id, f'Изменение данных.')
write_to_support(message)
if __name__ == '__main__':
print('Бот запущен!')
bot.infinity_polling()
Отлично, всё работает!
Подведём итого. Сегодня мы изучили:
Изучили фильтрацию запросов через лямбду
Написали Reply клавиатуру
Написали Inline клавиатуру
Отличный результат, практически все основные возможности изучены, осталось совсем чуть-чуть.
<<< Предыдущий урок
Ну вот, третья часть закончена! Можно считать, что все основные возможности клавиатуры изучены. Конечно, это не весь возможный функционал, но основная его часть.
Увидимся в следующей статье, в который мы полностью доделаем нашего бота и приготовим его к деплою на сервер.
Код с этого урока на GitHub: https://github.com/Ryize/CollectClientsFeedbackBot