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

Вступление

Доброго времени суток. Телеграмм — божественный месседж скаченный у каждого на телефонах/компьютерах и не только. После прочтение этой статьи вы научитесь создавать кнопки для вашего телеграмм бота. Желаю удачи, в прочтении!)

Предупреждение

Забыл предупредить, в этой статье не будет сказано о создание бота, получение токена через BotFather. Для этого прочитайте документацию библиотеки или посмотрите ролики на эту тему в ютубе(Для удобство ссылку на документацию на русском языке библиотеки pyTelegramBotAPI оставлю в описании). И так, вернемся к теме.

Создание url кнопки

Url кнопки используются, когда хотим создать кнопку при клике которой, пользователь переходил на сайт. Пример:

нажима на «перейти», телеграмм перекинет его на сайт, который вы оставили при создание url кнопки, но как же собственно создать? Легко!

import telebot
from telebot import types # для указание типов
import config

bot = telebot.TeleBot(config.token) # токен лежит в файле config.py

@bot.message_handler(commands=['start']) #создаем команду
def start(message):
    markup = types.InlineKeyboardMarkup()
    button1 = types.InlineKeyboardButton("Сайт Хабр", url='https://habr.com/ru/all/')
    markup.add(button1)
    bot.send_message(message.chat.id, "Привет, {0.first_name}! Нажми на кнопку и перейди на сайт)".format(message.from_user), reply_markup=markup)kup)
bot.polling(none_stop=True)

при создание url-кнопки используется тип InlineKeyboardMarkup, в который мы добавляем кнопку и с помощью reply_markup=markup выводим это в чат(обязательно не забудьте указать это, иначе ваша кнопка просто не будете отображаться).

Создание Reply кнопки

Я не знаю как корректно называются кнопки использующие тип ReplyKeyboardMarkup, но я много практиковался в их создание и покажу вам, как это делается. Вот пример Reply кнопок:

Вот весь код самого бота:

import telebot
from telebot import types # для указание типов
import config

bot = telebot.TeleBot(config.token)

@bot.message_handler(commands=['start'])
def start(message):
    markup = types.ReplyKeyboardMarkup(resize_keyboard=True)
    btn1 = types.KeyboardButton("👋 Поздороваться")
    btn2 = types.KeyboardButton("❓ Задать вопрос")
    markup.add(btn1, btn2)
    bot.send_message(message.chat.id, text="Привет, {0.first_name}! Я тестовый бот для твоей статьи для habr.com".format(message.from_user), reply_markup=markup)
    
@bot.message_handler(content_types=['text'])
def func(message):
    if(message.text == "👋 Поздороваться"):
        bot.send_message(message.chat.id, text="Привеет.. Спасибо что читаешь статью!)")
    elif(message.text == "❓ Задать вопрос"):
        markup = types.ReplyKeyboardMarkup(resize_keyboard=True)
        btn1 = types.KeyboardButton("Как меня зовут?")
        btn2 = types.KeyboardButton("Что я могу?")
        back = types.KeyboardButton("Вернуться в главное меню")
        markup.add(btn1, btn2, back)
        bot.send_message(message.chat.id, text="Задай мне вопрос", reply_markup=markup)
    
    elif(message.text == "Как меня зовут?"):
        bot.send_message(message.chat.id, "У меня нет имени..")
    
    elif message.text == "Что я могу?":
        bot.send_message(message.chat.id, text="Поздороваться с читателями")
    
    elif (message.text == "Вернуться в главное меню"):
        markup = types.ReplyKeyboardMarkup(resize_keyboard=True)
        button1 = types.KeyboardButton("👋 Поздороваться")
        button2 = types.KeyboardButton("❓ Задать вопрос")
        markup.add(button1, button2)
        bot.send_message(message.chat.id, text="Вы вернулись в главное меню", reply_markup=markup)
    else:
        bot.send_message(message.chat.id, text="На такую комманду я не запрограммировал..")

bot.polling(none_stop=True)

И так. Что бы создать Replay кнопку, нужно создать переменную, я назвал ее markup(9 строчка кода) в нее помещаем types.ReplyKeyboardMarkup(resize_keyboard=True). Resize_keybord=True выполняет функцию адаптации(я всегда указываю и вам советую тоже).После этого мы создаем переменную уже с самими кнопками и их текстом(10, 11 строчка) и затем добавляем эти переменные коммандой markup.add(__имя ваших переменных__). Коммандой bot.send_message(message.chat.id, text=»Привет, {0.first_name}! Я тестовый бот для твоей статьи для habr.com».format(message.from_user), reply_markup=markup) и обязательно не забывайте добавлять reply_markup=markup, иначе просто кнопки не будут отображаться.

Заключение

Большое спасибо вам за прочтение. Не судите строго, это моя первая статья, рассказал я скорее всего не очень, и только поверхностно. Если хотите углубиться в эту тему, почитайте документацию, кстати, вот документация по pyTelegramBotAPI на русском:

  • https://github-com.translate.goog/eternnoir/pyTelegramBotAPI?_x_tr_sl=auto&_x_tr_tl=ru&_x_tr_hl=ru&_x_tr_pto=nui

    при переходе вниз скролите и вы увидите документацию.

    пишите комменты с отзывами, буду рад их прочитать.

  • Мой телеграмм канал: https://t.me/it_diaryy

Кнопки¶

В этой главе мы познакомимся с такой замечательной фичей Telegram-ботов, как кнопки. Прежде всего, чтобы избежать
путаницы, определимся с названиями. То, что цепляется к низу экрана вашего устройства, будем называть обычными
кнопками, а то, что цепляется непосредственно к сообщениям, назовём инлайн-кнопками. Ещё раз картинкой:

Два вида кнопок

Обычные кнопки¶

Кнопки как шаблоны¶

Этот вид кнопок появился вместе с Bot API в далёком 2015 году и представляет собой не что иное, как шаблоны сообщений
(за исключением нескольких особых случаев, но о них позже). Принцип простой: что написано на кнопке, то и будет отправлено
в текущий чат. Соответственно, чтобы обработать нажатие такой кнопки, бот должен распознавать входящие текстовые сообщения.

Напишем хэндлер, который будет при нажатии на команду /start отправлять сообщение с двумя кнопками:

# from aiogram import types
@dp.message_handler(commands="start")
async def cmd_start(message: types.Message):
    keyboard = types.ReplyKeyboardMarkup()
    button_1 = types.KeyboardButton(text="С пюрешкой")
    keyboard.add(button_1)
    button_2 = "Без пюрешки"
    keyboard.add(button_2)
    await message.answer("Как подавать котлеты?", reply_markup=keyboard)

Обратите внимание, что т.к. обычные кнопки суть шаблоны сообщений, то их можно создавать не только как объекты KeyboardButton,
но и как обычные строки.
Что ж, запустим бота и обалдеем от громадных кнопок:

Очень большие обычные кнопки

Как-то некрасиво. Во-первых, хочется сделать кнопки поменьше, а во-вторых, расположить их горизонтально.
Почему вообще они такие большие? Дело в том, что по умолчанию «кнопочная» клавиатура должна занимать на смартфонах столько
же места, сколько и обычная буквенная. Для уменьшения кнопок к объекту клавиатуры надо указать дополнительный
параметр resize_keyboard=True.
Но как заменить вертикальные кнопки на горизонтальные? С точки зрения Bot API, клавиатура — это массив массивов
кнопок, а если говорить проще, массив строк. Метод add() при каждом вызове создаёт новую строку (ряд) и принимает
произвольное число аргументов по количеству желаемых кнопок в строке. Перепишем наш код, чтобы было красиво:

@dp.message_handler(commands="start")
async def cmd_start(message: types.Message):
    keyboard = types.ReplyKeyboardMarkup(resize_keyboard=True)
    buttons = ["С пюрешкой", "Без пюрешки"]
    keyboard.add(*buttons)
    await message.answer("Как подавать котлеты?", reply_markup=keyboard)

Обратите внимание на конструкцию *buttons. Здесь вам не C++ и звёздочка используется для распаковки списка.
Подробнее об операторах * и ** можно прочитать
здесь.

Смотрим — действительно красиво:

Кнопки в один ряд

Осталось научить бота реагировать на нажатие таких кнопок. Как уже было сказано выше, необходимо делать проверку
на полное совпадение текста. Сделаем это двумя способами: через специальный фильтр Text и обычной лямбдой:

# from aiogram.dispatcher.filters import Text
@dp.message_handler(Text(equals="С пюрешкой"))
async def with_puree(message: types.Message):
    await message.reply("Отличный выбор!")


@dp.message_handler(lambda message: message.text == "Без пюрешки")
async def without_puree(message: types.Message):
    await message.reply("Так невкусно!")

Реакция на нажатие кнопок

Чтобы удалить кнопки, необходимо отправить новое сообщение со специальной «удаляющей» клавиатурой типа
ReplyKeyboardRemove. Например: await message.reply("Отличный выбор!", reply_markup=types.ReplyKeyboardRemove())

У объекта обычной клавиатуры есть ещё две полезных опции:
one_time_keyboard для скрытия кнопок после нажатия и selective для показа клавиатуры лишь некоторым участникам группы.
Их использование остаётся для самостоятельного изучения.

Помимо стандартных опций, описанных выше, aiogram немного расширяет функциональность клавиатур параметром row_width.
При его использовании, фреймворк автоматически разобьёт массив кнопок на строки по N элементов в каждой, где N —
значение row_width, например, row_width=2. Попробуйте!

Специальные обычные кнопки¶

По состоянию на конец ужасного 2020 года в Telegram существует три специальных вида обычных кнопок, не являющихся шаблонами:
для отправки текущей геолокации, для отправки своего номера телефона и ярлык для создания опроса/викторины. Для первых двух
типов достаточно установить булевый флаг, а для опросов и викторин нужно передать специальный тип KeyboardButtonPollType
и, по желанию, указать тип создаваемого объекта.

Впрочем, проще один раз увидеть код:

@dp.message_handler(commands="special_buttons")
async def cmd_special_buttons(message: types.Message):
    keyboard = types.ReplyKeyboardMarkup(resize_keyboard=True)
    keyboard.add(types.KeyboardButton(text="Запросить геолокацию", request_location=True))
    keyboard.add(types.KeyboardButton(text="Запросить контакт", request_contact=True))
    keyboard.add(types.KeyboardButton(text="Создать викторину",
                                      request_poll=types.KeyboardButtonPollType(type=types.PollType.QUIZ)))
    await message.answer("Выберите действие:", reply_markup=keyboard)

Инлайн-кнопки¶

URL-кнопки и колбэки¶

В отличие от обычных кнопок, инлайновые цепляются не к низу экрана, а к сообщению, с которым были отправлены.
В этой главе мы рассмотрим два типа таких кнопок: URL и Callback. Ещё один — Switch — будет рассмотрен
в главе про инлайн-режим.

Login- и Pay-кнопки в книге рассматриваться не будут вообще. Если у кого-то есть желание помочь хотя бы
с рабочим кодом для авторизации или оплаты, пожалуйста, создайте Pull Request на
GitHub. Спасибо!

Самые простые инлайн-кнопки относятся к типу URL, т.е. «ссылка». Поддерживаются только протоколы HTTP(S) и tg://

@dp.message_handler(commands="inline_url")
async def cmd_inline_url(message: types.Message):
    buttons = [
        types.InlineKeyboardButton(text="GitHub", url="https://github.com"),
        types.InlineKeyboardButton(text="Оф. канал Telegram", url="tg://resolve?domain=telegram")
    ]
    keyboard = types.InlineKeyboardMarkup(row_width=1)
    keyboard.add(*buttons)
    await message.answer("Кнопки-ссылки", reply_markup=keyboard)

А если хотите обе кнопки в ряд, то уберите row_width=1 (тогда будет использоваться значение по умолчанию 3).

С URL-кнопками больше обсуждать, по сути, нечего, поэтому перейдём к гвоздю сегодняшней программы — Callback-кнопкам.
Это очень мощная штука, которую вы можете встретить практически везде. Кнопки-реакции у постов (лайки), меню у @BotFather
и т.д. Суть в чём: у колбэк-кнопок есть специальное значение (data), по которому ваше приложение опознаёт, что нажато и что надо сделать.
И выбор правильного data очень важен! Стоит также отметить, что, в отличие от обычных кнопок, нажатие на колбэк-кнопку
позволяет сделать практически что угодно, от заказа пиццы до перезагрузки сервера.

Напишем хэндлер, который по команде /random будет отправлять сообщение с колбэк-кнопкой:

@dp.message_handler(commands="random")
async def cmd_random(message: types.Message):
    keyboard = types.InlineKeyboardMarkup()
    keyboard.add(types.InlineKeyboardButton(text="Нажми меня", callback_data="random_value"))
    await message.answer("Нажмите на кнопку, чтобы бот отправил число от 1 до 10", reply_markup=keyboard)

Но как же обработать нажатие? Если раньше мы использовали message_handler для обработки входящих сообщений, то теперь
будем использовать callback_query_handler для обработки колбэков. Ориентироваться будем на «значение» кнопки, т.е. на
её data:

@dp.callback_query_handler(text="random_value")
async def send_random_value(call: types.CallbackQuery):
    await call.message.answer(str(randint(1, 10)))

Важно

Несмотря на то, что параметр кнопки callback_data, а значение data лежит в одноимённом поле data
объекта CallbackQuery, собственный фильтр aiogram называется text.

Реакция на нажатие колбэк-кнопки

Ой, а что это за часики? Оказывается, сервер Telegram ждёт от нас подтверждения о доставке колбэка, иначе в течение 30
секунд будет показывать специальную иконку. Чтобы скрыть часики, нужно вызвать метод answer() у колбэка (или использовать
метод API answer_callback_query()). В общем случае, в метод answer() можно ничего не передавать, но можно вызвать
специальное окошко (всплывающее сверху или поверх экрана):

@dp.callback_query_handler(text="random_value")
async def send_random_value(call: types.CallbackQuery):
    await call.message.answer(str(randint(1, 10)))
    await call.answer(text="Спасибо, что воспользовались ботом!", show_alert=True)
    # или просто await call.answer()

Всплывающее окно при нажатии на колбэк-кнопку

Обратите внимание

В функции send_random_value мы вызывали метод answer() не у message, а у call.message. Это связано с тем,
что колбэк-хэндлеры работают не с сообщениями (тип Message),
а с колбэками (тип CallbackQuery), у которого другие поля, и
само сообщение — всего лишь его часть. Учтите также, что message — это сообщение, к которому была прицеплена
кнопка (т.е. отправитель такого сообщения — сам бот). Если хотите узнать, кто нажал на кнопку, смотрите
поле from (в вашем коде это будет call.from_user, т.к. слово from зарезервировано в Python)

Когда вызывать answer()?

В общем случае, главное — просто не забыть сообщить Telegram о получении колбэк-запроса, но я рекомендую ставить
вызов answer() в самом конце, и вот почему: если вдруг в процессе обработки колбэка случится какая-то ошибка и
бот нарвётся на необработанное исключение, пользователь увидит неубирающиеся полминуты часики и поймёт, что что-то
не так. В противном случае, часики исчезнут, а пользователь останется в неведении, выполнился его запрос успешно или нет.

Перейдём к примеру посложнее. Пусть пользователю предлагается сообщение с числом 0, а внизу три кнопки: +1, -1 и Подтвердить.
Первыми двумя он может редактировать число, а последняя удаляет всю клавиатуру, фиксируя изменения. Хранить значения будем
в памяти в словаре (про конечные автоматы поговорим как-нибудь в другой раз).

# Здесь хранятся пользовательские данные.
# Т.к. это словарь в памяти, то при перезапуске он очистится
user_data = {}

def get_keyboard():
    # Генерация клавиатуры.
    buttons = [
        types.InlineKeyboardButton(text="-1", callback_data="num_decr"),
        types.InlineKeyboardButton(text="+1", callback_data="num_incr"),
        types.InlineKeyboardButton(text="Подтвердить", callback_data="num_finish")
    ]
    # Благодаря row_width=2, в первом ряду будет две кнопки, а оставшаяся одна
    # уйдёт на следующую строку
    keyboard = types.InlineKeyboardMarkup(row_width=2)
    keyboard.add(*buttons)
    return keyboard

async def update_num_text(message: types.Message, new_value: int):
    # Общая функция для обновления текста с отправкой той же клавиатуры
    await message.edit_text(f"Укажите число: {new_value}", reply_markup=get_keyboard())

@dp.message_handler(commands="numbers")
async def cmd_numbers(message: types.Message):
    user_data[message.from_user.id] = 0
    await message.answer("Укажите число: 0", reply_markup=get_keyboard())

@dp.callback_query_handler(Text(startswith="num_"))
async def callbacks_num(call: types.CallbackQuery):
    # Получаем текущее значение для пользователя, либо считаем его равным 0
    user_value = user_data.get(call.from_user.id, 0)
    # Парсим строку и извлекаем действие, например `num_incr` -> `incr`
    action = call.data.split("_")[1]
    if action == "incr":
        user_data[call.from_user.id] = user_value+1
        await update_num_text(call.message, user_value+1)
    elif action == "decr":
        user_data[call.from_user.id] = user_value-1
        await update_num_text(call.message, user_value-1)
    elif action == "finish":
        # Если бы мы не меняли сообщение, то можно было бы просто удалить клавиатуру
        # вызовом await call.message.delete_reply_markup().
        # Но т.к. мы редактируем сообщение и не отправляем новую клавиатуру, 
        # то она будет удалена и так.
        await call.message.edit_text(f"Итого: {user_value}")
    # Не забываем отчитаться о получении колбэка
    await call.answer()

И, казалось бы, всё работает:

Всё работает?

Но теперь представим, что ушлый пользователь сделал следующее: вызвал команду /numbers (значение 0), увеличил значение
до 1, снова вызвал /numbers (значение сбросилось до 0) и отредактировал нажал кнопку «+1» на первом сообщении.
Что произойдёт? Бот по-честному отправит запрос на редактирование текста со значением 1, но т.к. на том сообщении
уже стоит цифра 1, то Bot API вернёт ошибку, что старый и новый тексты совпадают, а бот словит исключение:
aiogram.utils.exceptions.MessageNotModified: Message is not modified: specified new message content and reply markup
are exactly the same as a current content and reply markup of the message

Всё работает?

С этой ошибкой вы, скорее всего, будете поначалу часто сталкиваться, пытаясь редактировать сообщения. Но, в действительности,
решается проблема очень просто: мы проигнорируем исключение MessageNotModified. Из первой главы вы
уже знаете о такой прекрасной штуке, как errors_handler, но в этот раз мы поступим чуть иначе и перепишем
функцию update_num_text() следующим образом:

# from aiogram.utils.exceptions import MessageNotModified
# from contextlib import suppress

async def update_num_text(message: types.Message, new_value: int):
    with suppress(MessageNotModified):
        await message.edit_text(f"Укажите число: {new_value}", reply_markup=get_keyboard())

Если теперь вы попробуете повторить пример выше, то указанное исключение в этом блоке кода бот просто-напросто проигнорирует.

Фабрика колбэков¶

В aiogram существует т.н. фабрика колбэков. Вы создаёте объект CallbackData, указываете ему префикс и произвольное
количество доп. аргументов, которые в дальнейшем указываете при создании колбэка для кнопки.
Например, рассмотрим следующий объект:

# from aiogram.utils.callback_data import CallbackData
cb= CallbackData("post", "id", "action")

Тогда при создании кнопки вам надо указать её параметры так:

button =  types.InlineKeyboardButton(
    text="Лайкнуть", 
    callback_data=cb.new(id=5, action="like")
)

В примере выше в кнопку запишется callback_data, равный post:5:like, а хэндлер на префикс post будет выглядеть так:

@dp.callback_query_handler(cb.filter())
async def callbacks(call: types.CallbackQuery, callback_data: dict):
    post_id = callback_data["id"]
    action = callback_data["action"]

В предыдущем примере с числами мы грамотно выбрали callback_data, поэтому смогли легко запихнуть все обработчики
в один хэндлер. Но можно логически разнести обработку инкремента и декремента от обработки нажатия на кнопку «Подтвердить».
Для этого в фильтре фабрики можно указать желаемые значения какого-либо параметра. Давайте перепишем наш пример
с использоваанием фабрики:

# fabnum - префикс, action - название аргумента, которым будем передавать значение
callback_numbers = CallbackData("fabnum", "action")


def get_keyboard_fab():
    buttons = [
        types.InlineKeyboardButton(text="-1", callback_data=callback_numbers.new(action="decr")),
        types.InlineKeyboardButton(text="+1", callback_data=callback_numbers.new(action="incr")),
        types.InlineKeyboardButton(text="Подтвердить", callback_data=callback_numbers.new(action="finish"))
    ]
    keyboard = types.InlineKeyboardMarkup(row_width=2)
    keyboard.add(*buttons)
    return keyboard


async def update_num_text_fab(message: types.Message, new_value: int):
    with suppress(MessageNotModified):
        await message.edit_text(f"Укажите число: {new_value}", reply_markup=get_keyboard_fab())


@dp.message_handler(commands="numbers_fab")
async def cmd_numbers(message: types.Message):
    user_data[message.from_user.id] = 0
    await message.answer("Укажите число: 0", reply_markup=get_keyboard_fab())


@dp.callback_query_handler(callback_numbers.filter(action=["incr", "decr"]))
async def callbacks_num_change_fab(call: types.CallbackQuery, callback_data: dict):
    user_value = user_data.get(call.from_user.id, 0)
    action = callback_data["action"]
    if action == "incr":
        user_data[call.from_user.id] = user_value + 1
        await update_num_text_fab(call.message, user_value + 1)
    elif action == "decr":
        user_data[call.from_user.id] = user_value - 1
        await update_num_text_fab(call.message, user_value - 1)
    await call.answer()


@dp.callback_query_handler(callback_numbers.filter(action=["finish"]))
async def callbacks_num_finish_fab(call: types.CallbackQuery):
    user_value = user_data.get(call.from_user.id, 0)
    await call.message.edit_text(f"Итого: {user_value}")
    await call.answer()

На этом глава про кнопки окончена, но про некоторые других их виды мы поговорим в следующих главах.

#статьи

  • 11 окт 2022

  • 0

Продолжаем писать чат-бота для Telegram — добавляем кнопки и интерактив.

Иллюстрация: Катя Павловская для Skillbox Media

Антон Яценко

Изучает Python, его библиотеки и занимается анализом данных. Любит путешествовать в горах.

В первой части урока по чат-ботам для Telegram мы создали на Python эхо-бота с помощью библиотеки aiogram. Сам эхо-бот работает просто, а его функция очевидна из названия: в ответ на сообщение пользователя он присылает тот же текст. Если вы ещё не читали первую часть, начните с неё.

Во второй части урока поработаем над меню: добавим для пользователей клавиатуру с быстрыми ответами и инлайн-кнопки для перехода на сайт Skillbox. Начнём с создания меню, но сначала разберёмся с видами возможных клавиатур.

Библиотека aiogram позволяет создать на Python клавиатуры двух видов, отличающиеся друг от друга расположением кнопок:

  • Reply-кнопки для шаблонных ответов, которые закрепляются вместо основной клавиатуры на экране. Часто используются в чат-ботах как меню. Создаются с помощью метода ReplyKeyboardMarkup.
  • Инлайн-кнопки, связанные с сообщениями в чате. При этом пользователь видит и основную клавиатуру. Создаются с помощью метода InlineKeyboardMarkup.

Наш эхо-бот для Telegram сейчас позволяет только отправлять текстовые сообщения и получать их обратно. Давайте проапгрейдим его и добавим кнопки с готовыми сообщениями, которые не надо вводить самому. Это будут reply-кнопки.

Нам понадобится класс ReplyKeyboardMarkup — для начала импортируем его и дополнительные необходимые классы:

from aiogram.types import ReplyKeyboardRemove, ReplyKeyboardMarkup, KeyboardButton

ReplyKeybordRemove и ReplyKeyboardMarkup позволяют создавать и удалять клавиатуру, а класс KeyboardButton используется для добавления кнопок.

Теперь создадим кнопки с готовыми фразами. Напишем код и разберёмся в нём:

@dp.message_handler(commands=['start'])
async def send_welcome(message: types.Message):
   kb = [
       [
           types.KeyboardButton(text="Сможешь повторить это?"),
           types.KeyboardButton(text="А это?")
       ],
   ]
   keyboard = types.ReplyKeyboardMarkup(keyboard=kb)
 
   await message.reply("Привет!nЯ Эхобот от Skillbox!nОтправь мне любое сообщение, а я тебе обязательно отвечу.", reply_markup=keyboard)

Сначала создадим в нашем первом декораторе список kb, который будет хранить кнопки. Кнопка в aiogram создаётся с помощью types.KeyboardButton(text=» «), где в параметре text мы передаём отображаемое название кнопки.

После этого необходимо создать клавиатуру и рассказать ей про наши кнопки. Делается это с помощью метода types.ReplyKeyboardMarkup(keyboard=list), где вместо list записывается название списка с кнопками — в нашем случае это список kb.

Теперь остаётся показать клавиатуру в Telegram-чате. Для этого добавляем в ответ строку reply_markup=keyboard, которая отображает клавиатуру после команды /start. Теперь при запуске бота мы видим, что в чате появились обе кнопки из списка kb:

Скриншот: aiogram / Skillbox Media

Если нажать на любую кнопку, текст кнопки отправится в чат, а Telegram-бот пришлёт в ответ эту же фразу:

Скриншот: aiogram / Skillbox Media

Можно создавать сколько угодно шаблонов, а также связывать кнопку с новыми действиями. Попробуем это на примере инлайн-клавиатур.

Инлайн-кнопки отличаются от обычных тем, что связаны не с областью клавиатуры в мессенджере, а с каким-то сообщением в Telegram-чате. Самый простой пример инлайн-кнопки — это меню в канале @BotFather, с помощью которого мы создавали токен для доступа к API Telegram. Например, вот так в нём выглядит инлайн-меню с уже созданными ботами:

Скриншот: aiogram / Skillbox Media

Создадим на Python для нашего бота инлайн-кнопки со ссылками на Skillbox Media и курсы по программированию. Для этого вернёмся к разделу с импортами в коде и добавим ещё одну строку, чтобы можно было использовать необходимые классы:

from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton

InlineKeyboardMarkup пригодится для инициализации инлайн-кнопок, а InlineKeyboardButton — для их создания.

Теперь создадим сами кнопки:

urlkb = InlineKeyboardMarkup(row_width=1)
urlButton = InlineKeyboardButton(text='Перейти в блог Skillbox', url='https://skillbox.ru/media/code/')
urlButton2 = InlineKeyboardButton(text='Перейти к курсам Skillbox', url='https://skillbox.ru/code/')
urlkb.add(urlButton,urlButton2)
 
@dp.message_handler(commands='ссылки')
async def url_command(message: types.Message):
   await message.answer('Полезные ссылки:', reply_markup=urlkb)

Разберёмся в коде построчно. Сначала мы инициализируем клавиатуру с помощью InlineKeyboardMarkup, передав в качестве аргумента row_width число 1. Оно определяет, сколько кнопок будет находиться в одном ряду. Так как у нас надписи на кнопках длинные, лучше поместить их друг под другом.

После этого создаём для каждой кнопки отдельную переменную и инициализируем класс Button. В параметрах указываем:

  • text — текст, который будет показан на кнопке в мессенджере;
  • url — URL страницы, на которую пользователь будет переходить при нажатии на кнопку.

С помощью метода add добавляем две кнопки к уже созданной клавиатуре. Теперь необходимо написать message_handler, который будет вызывать в Telegram эти инлайн-кнопки. Делаем это по аналогии с обычными кнопками — с той лишь разницей, что в качестве команды вызова укажем ссылки, а в параметре reply_markup передадим название нашей клавиатуры — urlkb.

Повторно запустим нашего бота и посмотрим на результат:

Скриншот: aiogram / Skillbox Media

Всё получилось. Теперь инлайн-клавиатура появляется при отправке команды /ссылки в бот.

В нашем эхо-боте для Telegram появилось два вида меню, написанных на Python: reply-кнопки для быстрых сообщений и инлайн-кнопки для перехода на блог и сайт Skillbox. Для создания сложных ботов — например, ботов онлайн-магазинов — можно самостоятельно изучить документацию к библиотеке aiogram: попробовать новые классы, методы и объекты.

Учись бесплатно:
вебинары по программированию, маркетингу и дизайну.

Участвовать

Научитесь: Профессия Python-разработчик
Узнать больше

Это статья из серии о телеграм-ботах. Я по частям расскажу о важных моментах в pytelegrambotapi. Сразу полезная информация: я начал учиться делать ботов по этой статье и всем ее рекомендую.

Виды

Кнопки в телеграм бывают двух видов — inline и обычные (reply).

Обычные

Обычные вы видите вместо клавиатуры, все, что они делают, как правило, — отправляют текстовое сообщение боту.

обычные кнопки

Но, перед тем, как сделать кнопку, нужно создать клавиатуру. Для обычных кнопок она одна, для inline другая, вот обычная:

keyboard = types.ReplyKeyboardMarkup(row_width=1, resize_keyboard=False, one_time_keyboard=True)

Параметры:

resize_keyboard: bool — необязательный | будет ли клавиатура растягиваться (по умолчанию: без значения)

one_time_keyboard: bool — необязательный разовая ли клавиатура (по умолчанию: без значения)

selective: bool — необязательный | кому показывать (по умолчанию: без значения)

row_width: int — обязательный | количество столбцов кнопок (по умолчанию: 3)

input_field_placeholder: str — необязательный | заменяет текст-заглушку в поле ввода (по умолчанию: без значения)

Создав клавиатуру, создадим к ней кнопку:

stop = types.KeyboardButton(text=’отмена ❌’)

text: str — обязательный | текст кнопки. Если указан только он, то кнопка будет просто отправлять текст при нажатии.

request_contact: bool — необязательный | если указан как true то пользователь при нажатии отправит свой контакт. Доступен только в приватных чатах.

request_location: bool — необязательный | если указан как true, то пользователь при нажатии отправит свою локацию. Доступен только в приватных чатах.

request_poll: bool — необязательный | если указан как true, то пользователь при нажатии отправит свою локацию. Доступен только в приватных чатах.

web_app: WebAppInfo — необязательный | если указан как true, то при нажатии откроется страница указанного веб-приложения — про это есть отдельная статья. Доступен только в приватных чатах.

После создания кнопки ее нужно добавить в клавиатуру вот так:

keyboard.add(stop)

И далее дать эту клавиатуру пользователю с сообщением:

bot.send_message(message.chat.id, ‘Клавиатура с одной кнопкой, reply_markup=keyboard)

  • Отправить можно только одну клавиатуру за раз
  • Это актуально для всех типов клавиатур.

Inline

Inline-кнопки вы видите под сообщением.

Inline-кнопки

Клавиатура для них делается вот так:

keyboard = types.InlineKeyboardMarkup(row_width=2)

у нее только один параметр:

row_width: int — обязательный | количество столбцов кнопок (по умолчанию: 3)

Теперь делаем кнопку:

add = types.InlineKeyboardButton(text=»Принять ✅», callback_data = str({‘user_id’: user_id, ‘add’: ‘1’}))

inline-кнопки бывают нескольких видов.

1) Отправляющие данные. В кнопке записаны какие-то данные в формате строки. При нажатии их можно отловить и среагировать на них.

2) Ссылки — просто открывают сайт в вашем браузере.

3) WebApp — открывают веб-приложение в отдельном окне, опять-таки у меня есть статья об этом.

Есть и другие виды. Можете посмотреть актуальные по ссылке.

Рассмотрим самый часто используемый — отправляющий данные.

Чтобы создать такую нам нужно указать просто текст и данные в виде строки. А чтобы отловить это действие и получить данные нужно сделать следующее:

@bot.callback_query_handler(func=lambda call: True) //вешаем обработчик событий на нажатие всех inline-кнопок
def callback_inline(call):
if call.data: //проверяем есть ли данные если да, далаем с ними что-то.

Вот и все, мы выяснили какие бывают основные кнопки и как ими пользоваться.

Нужен VDS-сервер для хостинга ботов? — рекомендую sprintbox. По моему промокоду CASHGOK20 вы получите кэшбэк 20% при первом пополнении — не забудьте ввести его.

Успехов и всего доброго 🤟

Users can interact with your bot via buttons or even inline buttons, straight from inline messages in any chat.
This article describes the full button flow, using the MTProto API.

For a simplified description using the HTTP bot API, see here ».

Buttons

keyboardButton#a2fa4880 text:string = KeyboardButton;
keyboardButtonUrl#258aff05 text:string url:string = KeyboardButton;
keyboardButtonCallback#35bbdb6b flags:# requires_password:flags.0?true text:string data:bytes = KeyboardButton;
keyboardButtonRequestPhone#b16a6c29 text:string = KeyboardButton;
keyboardButtonRequestGeoLocation#fc796b3f text:string = KeyboardButton;
keyboardButtonSwitchInline#568a748 flags:# same_peer:flags.0?true text:string query:string = KeyboardButton;
keyboardButtonGame#50f41ccf text:string = KeyboardButton;
keyboardButtonBuy#afd93fbb text:string = KeyboardButton;
keyboardButtonUrlAuth#10b78d29 flags:# text:string fwd_text:flags.0?string url:string button_id:int = KeyboardButton;
inputKeyboardButtonUrlAuth#d02e7fd4 flags:# request_write_access:flags.0?true text:string fwd_text:flags.1?string url:string bot:InputUser = KeyboardButton;
keyboardButtonRequestPoll#bbc7515d flags:# quiz:flags.0?Bool text:string = KeyboardButton;

keyboardButtonRow#77608b83 buttons:Vector<KeyboardButton> = KeyboardButtonRow;

replyKeyboardHide#a03e5b85 flags:# selective:flags.2?true = ReplyMarkup;
replyKeyboardForceReply#86b40b08 flags:# single_use:flags.1?true selective:flags.2?true placeholder:flags.3?string = ReplyMarkup;
replyKeyboardMarkup#85dd99d1 flags:# resize:flags.0?true single_use:flags.1?true selective:flags.2?true rows:Vector<KeyboardButtonRow> placeholder:flags.3?string = ReplyMarkup;
replyInlineMarkup#48a30254 rows:Vector<KeyboardButtonRow> = ReplyMarkup;

message#38116ee0 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true from_scheduled:flags.18?true legacy:flags.19?true edit_hide:flags.21?true pinned:flags.24?true noforwards:flags.26?true id:int from_id:flags.8?Peer peer_id:Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?long reply_to:flags.3?MessageReplyHeader date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector<MessageEntity> views:flags.10?int forwards:flags.10?int replies:flags.23?MessageReplies edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long reactions:flags.20?MessageReactions restriction_reason:flags.22?Vector<RestrictionReason> ttl_period:flags.25?int = Message;

---functions---

messages.sendMessage#d9d75a4 flags:# no_webpage:flags.1?true silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true update_stickersets_order:flags.15?true peer:InputPeer reply_to_msg_id:flags.0?int message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector<MessageEntity> schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates;

Bots can attach a ReplyMarkup constructor to outgoing messages, to attach an inline keyboard or a custom reply keyboard:

  • replyKeyboardMarkup — Sends a custom reply keyboard.
    User clients receiving such a constructor should display a special keyboard with custom reply options.
  • replyKeyboardHide — Hides the custom reply keyboard.
    User clients receiving this constructor should hide the custom reply keyboard opened by replyKeyboardMarkup
  • replyKeyboardForceReply — Sends a force reply constructor
    User clients receiving a message with this constructor should act as if the user had clicked on the reply button of the message, displaying the reply UI.
  • replyInlineMarkup — Attaches an inline keyboard to the message, allowing users to send callback data to the bot without sending actual messages to the current chat.

Pressing buttons

keyboardButton#a2fa4880 text:string = KeyboardButton;
keyboardButtonUrl#258aff05 text:string url:string = KeyboardButton;
keyboardButtonCallback#35bbdb6b flags:# requires_password:flags.0?true text:string data:bytes = KeyboardButton;
keyboardButtonRequestPhone#b16a6c29 text:string = KeyboardButton;
keyboardButtonRequestGeoLocation#fc796b3f text:string = KeyboardButton;
keyboardButtonRequestPoll#bbc7515d flags:# quiz:flags.0?Bool text:string = KeyboardButton;
keyboardButtonSwitchInline#568a748 flags:# same_peer:flags.0?true text:string query:string = KeyboardButton;
keyboardButtonGame#50f41ccf text:string = KeyboardButton;
keyboardButtonBuy#afd93fbb text:string = KeyboardButton;
keyboardButtonUrlAuth#10b78d29 flags:# text:string fwd_text:flags.0?string url:string button_id:int = KeyboardButton;

// Used by bots to send a keyboardButtonUrlAuth
inputKeyboardButtonUrlAuth#d02e7fd4 flags:# request_write_access:flags.0?true text:string fwd_text:flags.1?string url:string bot:InputUser = KeyboardButton;

keyboardButtonRow#77608b83 buttons:Vector<KeyboardButton> = KeyboardButtonRow;

Both reply and inline keyboards are composed of a vector of rows, each row containing a vector of buttons, for each column.
Each row can have a different number of columns, and user clients should properly handle clicking buttons of every type.

Buttons available only in reply keyboards:

  • keyboardButton — Send a message to the chat, replying to the message that attached the reply keyboard
  • keyboardButtonRequestPhone — Only in private chats, send the current user’s contact to the chat, replying to the message that attached the reply keyboard
  • keyboardButtonRequestGeoLocation — Only in private chats, send the current user’s geolocation to the chat, replying to the message that attached the reply keyboard
  • keyboardButtonRequestPoll — Only in private chats, prompt the user to create and send a poll (or a quiz poll, depending on the quiz flag), replying to the message that attached the reply keyboard

Buttons available only in inline keyboards:

  • keyboardButtonUrl — Open the URL, showing a «Do you want to open this URL?» prompt (unless the URL is one of the internal URIs, in which case the URL should be opened right away)
  • keyboardButtonCallback — Send the callback data to the bot, optionally providing the user’s 2FA SRP payload for identity verification, see here for more info »
  • keyboardButtonSwitchInline
    • If keyboardButtonSwitchInline.same_peer is set, insert the bot’s username and keyboardButtonSwitchInline.query in the current chat’s input field, triggering an inline query.
    • If keyboardButtonSwitchInline.same_peer is not set, prompt the user to select one of their chats, and then insert the bot’s username and keyboardButtonSwitchInline.query in the current chat’s input field, triggering an inline query.
  • keyboardButtonGame — Open the game from the attached messageMediaGame constructor, for more info see here »
  • keyboardButtonBuy — Proceed to initiating the payment flow, for more info see here »
  • keyboardButtonUrlAuth — Log into a website using the user’s Telegram account, as specified here »

Callback queries

keyboardButtonCallback buttons can be used to send the specified data payload back to the bot, when they are clicked.
Additionally, a bot can verify a user’s identity by requiring they verify their 2FA password with SRP.

Sending a callback query

keyboardButtonGame#50f41ccf text:string = KeyboardButton;
keyboardButtonCallback#35bbdb6b flags:# requires_password:flags.0?true text:string data:bytes = KeyboardButton;

messages.botCallbackAnswer#36585ea4 flags:# alert:flags.1?true has_url:flags.3?true native_ui:flags.4?true message:flags.0?string url:flags.2?string cache_time:int = messages.BotCallbackAnswer;

---functions---

messages.getBotCallbackAnswer#9342ca07 flags:# game:flags.1?true peer:InputPeer msg_id:int data:flags.0?bytes password:flags.2?InputCheckPasswordSRP = messages.BotCallbackAnswer;

When the user clicks on a keyboardButtonCallback in a message sent by a bot, or generated by an inline query, messages.getBotCallbackAnswer should be called, passing the peer and ID of the message.
The same should happen when clicking on keyboardButtonGame buttons, with the difference that the game flag must be set instead of the data parameter.

Make sure to properly handle bot timeouts in the form of BOT_RESPONSE_TIMEOUT RPC errors, as the bot may be offline and unable to reply.

The returned messages.botCallbackAnswer constructor contains:

  • message if specified, a message that should be shown in a non-blocking toast notification
  • alert indicates whether the message should be shown as a dismissible prompt, instead of a simple toast notification
  • has_url Whether an URL is present
  • url if specified, the client should open the URL, without showing a confirmation prompt.
    This is safe and allowed, because here bots can only return:
    • Deep links to themselves »
    • Deep links to a valid game they own », if the bot has manually configured games, and the clicked button was a keyboardButtonGame.
  • native_ui whether to open game URLs in a WebView or in native UI.
  • cache_time specifies for how long should this answer be cached, client-side
SRP verification

If the requires_password flag is set, the SRP 2FA payload must also be generated and attached to the query, to verify the identity of the user.

Note that the bot will NOT be able to access your password or the SRP payload.

The SRP payload will be processed exclusively on the Telegram’s servers, simply returning an RPC error without passing the query to the bot if the verification fails.
This is just a way of verifying the identity of the user, mainly used by the official @botfather bot to allow securely transferring the ownership of a bot to another user.

Answering a callback query

updateBotCallbackQuery#b9cfc48d flags:# query_id:long user_id:long peer:Peer msg_id:int chat_instance:long data:flags.0?bytes game_short_name:flags.1?string = Update;

updateInlineBotCallbackQuery#691e9052 flags:# query_id:long user_id:long msg_id:InputBotInlineMessageID chat_instance:long data:flags.0?bytes game_short_name:flags.1?string = Update;

---functions---

messages.setBotCallbackAnswer#d58f130a flags:# alert:flags.1?true query_id:long message:flags.0?string url:flags.2?string cache_time:int = Bool;

After the user invokes messages.getBotCallbackAnswer, an updateBotCallbackQuery or updateInlineBotCallbackQuery is generated and sent to the bot, depending on whether the query originated from a normal message sent by the bot, or from a message sent from an inline query.

Either way, bots must reply to the query as quickly as possible using messages.setBotCallbackAnswer:

  • query_id is the query_id from messages.getBotCallbackAnswer, an updateBotCallbackQuery or updateInlineBotCallbackQuery
  • message, alert, url can contain messages and URLs to trigger different client behaviour, as specified above »
  • cache_time indicates the maximum amount of time in seconds that the result of the callback query may be cached by the client.

If a game_short_name is present in the update, the bot should return the URL of the game with the specified name.
The messages.setBotCallbackAnswer method must be called anyway, even if no message or url is returned, to avoid timeouts on the client.

Введение

Складывается ощущение, что 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 был написан текст он автоматически добавится после тега бота.

QQ Хабр! В этом гайде мы пройдемся по каждому шагу создания ботов в Telegram — от регистрации бота до публикации репозитория на GitHub. Некоторым может показаться, что все разжевано и слишком много элементарной информации, но этот гайд создан для новичков, хотя будет интересен и для тех, кто уже занимался разработкой в Telegram. Сегодня мы будем делать бота, который отвечает на заданные вопросы.

I. Регистрация бота

Прежде всего нужно зарегать бота. Для этого пишем боту @BotFather команду /newbot, после этого даем боту имя и тэг. После этих действий бот отправит нам токен, который никому давать нельзя.

На этом процесс регистрации бота завершен, можно приступать к самому интересному — кодингу

II. Кодинг

Итак, бота мы будем писать на python. Для начала установим библиотеку pytelegrambotapi. Для этого в командной строке (или в терминале, если у вас MacOS) пишем:

pip3 install pytelegrambotapi

После этого можно приступать, импортируем библиотеки и вводим токен:

import telebot

bot = telebot.TeleBot('BOT-TOKEN')

Вместо BOT-TOKEN пишем токен, который получили от BotFather

Сейчас можно уже и поговорить о кнопках

Кнопки

Для того, чтобы кнопки заработали нужно импортировать типы, для этого после импорта библиотек пишем:

from telebot import types

Бывает два вида кнопок, это:

  • Inline-кнопки

  • Keyboard-кнопки

Inline-кнопки

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

@bot.message_handler(commands = ['start'])
def url(message):
    markup = types.InlineKeyboardMarkup()
    btn1 = types.InlineKeyboardButton(text='Наш сайт', url='https://habr.com/ru/all/')
    markup.add(btn1)
    bot.send_message(message.from_user.id, "По кнопке ниже можно перейти на сайт хабра", reply_markup = markup)

Выглядит это так

Более подробно про такие кнопки можно почитать в этой статье

Keyboard-кнопки

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

@bot.message_handler(commands=['start'])
def start(message):

    markup = types.ReplyKeyboardMarkup(resize_keyboard=True)
    btn1 = types.KeyboardButton("???????? Русский")
    btn2 = types.KeyboardButton('???????? English')
    markup.add(btn1, btn2)
    bot.send_message(message.from_user.id, "???????? Выберите язык / ???????? Choose your language", reply_markup=markup)

Вот как это выглядит

Обратно к коду

Теперь уже точно можно вернуться к кодингу, давайте добавим стартовую команду и кнопку

@bot.message_handler(commands=['start'])
def start(message):

    markup = types.ReplyKeyboardMarkup(resize_keyboard=True)
    btn1 = types.KeyboardButton("???? Поздороваться")
    markup.add(btn1)
    bot.send_message(message.from_user.id, "???? Привет! Я твой бот-помошник!", reply_markup=markup)

Далее делаем реакцию бота на кнопки (здесь то уже есть комментарии)

@bot.message_handler(content_types=['text'])
def get_text_messages(message):

    if message.text == '???? Поздороваться':
        markup = types.ReplyKeyboardMarkup(resize_keyboard=True) #создание новых кнопок
        btn1 = types.KeyboardButton('Как стать автором на Хабре?')
        btn2 = types.KeyboardButton('Правила сайта')
        btn3 = types.KeyboardButton('Советы по оформлению публикации')
        markup.add(btn1, btn2, btn3)
        bot.send_message(message.from_user.id, '❓ Задайте интересующий вопрос', reply_markup=markup) #ответ бота

Теперь по этому примеру продолжаем плодить бота

elif message.text == 'Как стать автором на Хабре?':
    bot.send_message(message.from_user.id, 'Вы пишете первый пост, его проверяют модераторы, и, если всё хорошо, отправляют в основную ленту Хабра, где он набирает просмотры, комментарии и рейтинг. В дальнейшем премодерация уже не понадобится. Если с постом что-то не так, вас попросят его доработать.n nПолный текст можно прочитать по ' + '[ссылке](https://habr.com/ru/sandbox/start/)', parse_mode='Markdown')

elif message.text == 'Правила сайта':
    bot.send_message(message.from_user.id, 'Прочитать правила сайта вы можете по ' + '[ссылке](https://habr.com/ru/docs/help/rules/)', parse_mode='Markdown')

elif message.text == 'Советы по оформлению публикации':
    bot.send_message(message.from_user.id, 'Подробно про советы по оформлению публикаций прочитать по ' + '[ссылке](https://habr.com/ru/docs/companies/design/)', parse_mode='Markdown')

Так, стоп, сейчас нужно обсудить, как делать гиперссылки. Это делается просто, вот пример:

bot.send_message(message.from_user.id, 'Подробно про советы по оформлению публикаций прочитать по ' + '[ссылке](https://habr.com/ru/docs/companies/design/)', parse_mode='Markdown')

Как мы видим, чтобы сделать гиперссылку мы берем в квадратные скобки слово, которое будет ссылкой, а саму ссылку берем в круглые. В конце строки добавляем parse_mode='Markdown'

Когда мы дописали основной код нужно вставить важную строку

bot.polling(none_stop=True, interval=0) #обязательная для работы бота часть

Как сказано в комментарии, это строка обязательна для работы бота, чтобы он не отключался и работал постоянно.

Полностью наш код выглядит так:

import telebot
from telebot import types

bot = telebot.TeleBot('BOT-TOKEN')

@bot.message_handler(commands=['start'])
def start(message):

    markup = types.ReplyKeyboardMarkup(resize_keyboard=True)
    btn1 = types.KeyboardButton("???? Поздороваться")
    markup.add(btn1)
    bot.send_message(message.from_user.id, "???? Привет! Я твой бот-помошник!", reply_markup=markup)

@bot.message_handler(content_types=['text'])
def get_text_messages(message):

    if message.text == '???? Поздороваться':
        markup = types.ReplyKeyboardMarkup(resize_keyboard=True) #создание новых кнопок
        btn1 = types.KeyboardButton('Как стать автором на Хабре?')
        btn2 = types.KeyboardButton('Правила сайта')
        btn3 = types.KeyboardButton('Советы по оформлению публикации')
        markup.add(btn1, btn2, btn3)
        bot.send_message(message.from_user.id, '❓ Задайте интересующий вас вопрос', reply_markup=markup) #ответ бота


    elif message.text == 'Как стать автором на Хабре?':
        bot.send_message(message.from_user.id, 'Вы пишете первый пост, его проверяют модераторы, и, если всё хорошо, отправляют в основную ленту Хабра, где он набирает просмотры, комментарии и рейтинг. В дальнейшем премодерация уже не понадобится. Если с постом что-то не так, вас попросят его доработать.n nПолный текст можно прочитать по ' + '[ссылке](https://habr.com/ru/sandbox/start/)', parse_mode='Markdown')

    elif message.text == 'Правила сайта':
        bot.send_message(message.from_user.id, 'Прочитать правила сайта вы можете по ' + '[ссылке](https://habr.com/ru/docs/help/rules/)', parse_mode='Markdown')

    elif message.text == 'Советы по оформлению публикации':
        bot.send_message(message.from_user.id, 'Подробно про советы по оформлению публикаций прочитать по ' + '[ссылке](https://habr.com/ru/docs/companies/design/)', parse_mode='Markdown')


bot.polling(none_stop=True, interval=0) #обязательная для работы бота часть

Этот код я написал за 10 минут, при желании код можно сделать намного больше. Полноценный бот выглядит так. Этого бота я делал для конкурса и написал его за 3,5 часа.

Важно: если вы делаете полноценного бота, которого будете куда-то публиковать, очень стоит добавить в него эмодзи: кнопки, сообщения — все должно включать в себя эмодзи, это важно для красоты

III. Публикация репозитория на GitHub

Для этого на потребуется приложение GitHub Desktop

Создаем новый репозиторий, после этого в папке по умолчанию появится папка с названием вашего проекта. В нее закидываем файлы проекта и в приложении нажимаем кнопку Commit to main. После этого нажимаем на кнопку Publish Repository. Готово! При желании, можно создать Readme.md

IV. Заключение

Здесь я расписал все, что я вспомнил о разработке ботов для telegram, если есть, что предложить — комментарии открыты для вас. Надеюсь, вам это было полезно.

Источники:

Документация Telegram Bot API

Встроенные кнопки в Telegram Bot API — pyTelgramBotApi

Понравилась статья? Поделить с друзьями:
  • Как написать книжку для детей
  • Как написать книжечка или книжичка
  • Как написать книгу ютуб
  • Как написать книгу этапы
  • Как написать книгу чтобы ее читали