Телеграм-бот на Python: кнопки и меню с нуля

Телеграм-бот на Python: кнопки и меню с нуля Полезное

Телеграм-бот на Python — это программа, которая через Bot API Telegram принимает сообщения пользователя и отвечает на них: текстом, кнопками или меню команд. Ниже — дорожная карта от пустого файла до рабочего бота, у которого есть reply-клавиатура, inline-кнопки и меню команд. Сразу разберемся, чем reply-кнопки отличаются от inline, и что делать, если бот не отвечает.

Код написан под библиотеку python-telegram-bot версии 21-22 (async-ветка). API этих библиотек сильно менялся между версиями, поэтому примеры из старых статей (2021-2024) на свежем пакете часто не запускаются.

Дорожная карта: от нуля до рабочего бота

  1. Установить Python 3.9+ и библиотеку python-telegram-bot.
  2. Получить токен бота у BotFather в самом Telegram.
  3. Запустить минимальный бот, который отвечает на /start.
  4. Добавить reply-клавиатуру с быстрыми ответами.
  5. Добавить inline-кнопки со ссылками и действиями.
  6. Настроить меню команд (список у поля ввода).

Артефакт — один файл bot.py: запускаете python bot.py, и бот отвечает в чате.

Шаг 1-2: установка и токен BotFather

Библиотека ставится через pip. В терминале:

pip install "python-telegram-bot>=21,<23"

Токен нельзя придумать — его выдает бот BotFather. Откройте чат с @BotFather, отправьте /newbot, задайте имя и username (оканчивается на bot). В ответ придет строка вида 123456789:AA... — это токен для Bot API.

Токен — это пароль от бота. Не публикуйте его в репозитории и не коммитьте в git. Если засветили — выпустите новый командой /revoke в BotFather.

Шаг 3: минимальный рабочий бот целиком

Сначала — полный файл, который копируется и запускается. Подставьте токен в TOKEN:

# python-telegram-bot 21.x/22.x (async)
from telegram import Update
from telegram.ext import (
    Application, CommandHandler, MessageHandler, ContextTypes, filters
)

TOKEN = "ВСТАВЬТЕ_ТОКЕН_ОТ_BOTFATHER"

async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
    await update.message.reply_text("Привет! Я бот от Otus. Напиши мне что-нибудь.")

async def echo(update: Update, context: ContextTypes.DEFAULT_TYPE):
    # отвечает тем же текстом, что прислал пользователь
    await update.message.reply_text(update.message.text)

def main():
    app = Application.builder().token(TOKEN).build()
    app.add_handler(CommandHandler("start", start))
    app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, echo))
    app.run_polling()  # слушает Telegram, пока не остановите Ctrl+C

if __name__ == "__main__":
    main()

Ожидаемый результат: python bot.py запускается без ошибок, окно «висит». В чате /start дает ответ «Привет! Я бот от Otus…», а любое сообщение бот повторяет обратно.

Теперь по частям. Application — ядро бота (builder().token(...).build()). CommandHandler("start", start) связывает команду /start с функцией. MessageHandler(filters.TEXT & ~filters.COMMAND, echo) ловит любой текст, кроме команд. run_polling() запускает опрос серверов Telegram. Обработчики асинхронные (async def), ответ идет через await ...reply_text(...) — в версиях 20+ весь код бота асинхронный.

Reply-кнопки и inline-кнопки: в чем разница

В Telegram два вида кнопок, их легко перепутать — разведем явно.

Признак Reply-кнопки (ReplyKeyboardMarkup) Inline-кнопки (InlineKeyboardMarkup)
Где показываются На месте обычной клавиатуры устройства Прямо под конкретным сообщением
Что делает нажатие Отправляет в чат текст кнопки как обычное сообщение Не пишет в чат, а вызывает действие (callback) или ссылку
Класс кнопки KeyboardButton InlineKeyboardButton
Типичное применение Главное меню, быстрые ответы Ссылки, лайки, пагинация, подтверждение
Нужен отдельный обработчик Нет, приходит как текст Да, CallbackQueryHandler для callback-кнопок

Ориентир: нужна готовая фраза от пользователя — берите reply; нужен переход по ссылке или действие без записи в чат — inline.

Шаг 4: reply-клавиатура с быстрыми ответами

Reply-клавиатуру собирают из рядов, каждый ряд — список кнопок. Замените функцию start на эту:

from telegram import ReplyKeyboardMarkup, KeyboardButton

async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
    keyboard = ReplyKeyboardMarkup(
        [
            [KeyboardButton("Повтори это"), KeyboardButton("А это?")],
            [KeyboardButton("Помощь")],
        ],
        resize_keyboard=True,  # подгоняется под размер экрана
    )
    await update.message.reply_text("Выбери кнопку:", reply_markup=keyboard)

Результат: после /start под полем ввода появляются три кнопки в два ряда. Тап по «Повтори это» отправляет в чат этот текст, и его ловит наш echo. Отдельный обработчик для reply-кнопок не нужен: это обычные сообщения.

Шаг 5: inline-кнопки со ссылкой и действием

У inline-кнопки два режима: url — открыть ссылку, callback_data — передать боту метку действия. Добавьте команду /links:

from telegram import InlineKeyboardMarkup, InlineKeyboardButton
from telegram.ext import CallbackQueryHandler

async def links(update: Update, context: ContextTypes.DEFAULT_TYPE):
    keyboard = InlineKeyboardMarkup([
        [InlineKeyboardButton("Журнал Otus", url="https://otus.ru/journal/")],
        [InlineKeyboardButton("Показать привет", callback_data="say_hello")],
    ])
    await update.message.reply_text("Полезное:", reply_markup=keyboard)

async def on_click(update: Update, context: ContextTypes.DEFAULT_TYPE):
    query = update.callback_query
    await query.answer()  # обязательно: убирает "часики" на кнопке
    if query.data == "say_hello":
        await query.edit_message_text("Привет от Otus!")

Зарегистрируйте оба обработчика в main:

    app.add_handler(CommandHandler("links", links))
    app.add_handler(CallbackQueryHandler(on_click))

Результат: /links показывает две кнопки. «Журнал Otus» открывает ссылку. «Показать привет» не пишет в чат, но текст сообщения меняется на «Привет от Otus!». Без await query.answer() кнопка «крутится» у пользователя.

Шаг 6: меню команд бота

Меню команд — список у поля ввода, где видно доступные команды с описанием. Задается один раз через set_my_commands:

from telegram import BotCommand

async def setup_commands(app: Application):
    await app.bot.set_my_commands([
        BotCommand("start", "Запустить бота"),
        BotCommand("links", "Полезные ссылки"),
    ])

Подключается через post_init при сборке приложения:

    app = Application.builder().token(TOKEN).post_init(setup_commands).build()

Результат: появляется кнопка меню, при нажатии — список из трех команд с подписями. Обновление у клиентов Telegram может занять несколько минут.

Если не получилось: типовые симптомы

Диагностика по симптому:

  • Бот молчит на /start. Проверьте, что скрипт запущен и не упал, токен верный, и вы пишете своему боту (по username из BotFather).
  • Unauthorized / 401. Токен неверный или отозван — выпустите новый через /revoke.
  • ModuleNotFoundError: No module named 'telegram'. Библиотека не установлена в то окружение, где запускаете бота — повторите pip install в нужном venv.
  • Reply-кнопки не появились. На мобильном тапните иконку клавиатуры у поля ввода.
  • Inline-кнопка «крутится» после нажатия. Вы не вызвали await query.answer().
  • Ошибка про event loop в Jupyter. run_polling() рассчитан на запуск скриптом; в ноутбуке цикл событий уже занят — запускайте бот как отдельный .py-файл.

Выводы

  • Токен выдает BotFather в Telegram; хранить его как пароль, не публикуя в коде.
  • Reply-кнопки отправляют в чат готовый текст (обычные сообщения); inline-кнопки вызывают действие или ссылку и требуют CallbackQueryHandler.
  • Для inline-кнопок с callback обязателен await query.answer(), иначе кнопка зависает у пользователя.
  • Меню команд задается один раз через set_my_commands и обновляется у клиентов с задержкой.
  • Код зависит от версии библиотеки: примеры — под python-telegram-bot 21-22 (async); под другой пакет или мажорную версию синтаксис другой.

Где применяется / связь с практикой

Бот — частая первая практическая задача начинающего Python-разработчика: тут и работа с внешним API, и обработка событий, и асинхронный код. Та же логика масштабируется на рабочие сценарии: боты поддержки, уведомления, интеграции.

Освойте тему на практике

Чтобы разобраться в Python системно, а не только в ботах, — это тема курса Python Basic в Otus: основы языка, функции, работа с библиотеками. Посмотреть формат до старта помогают бесплатные вебинары Otus.

Смежные темы: Создание Телеграм-бота для генерации изображений с Docker и Stable Diffusion, Как научиться программировать с нуля: с чего начать.

FAQ

Можно ли писать бота на webhook, а не на polling?
Да. run_polling() удобен для разработки, а на боевом сервере с доменом и HTTPS обычно используют webhook (run_webhook). Логика обработчиков не меняется.

Reply-кнопки видны всем в групповом чате?
По умолчанию reply-клавиатура показывается адресату сообщения; в группах ее поведение регулирует параметр selective. Inline-кнопки видны всем под сообщением.

Чем aiogram отличается от python-telegram-bot?
Это две разные библиотеки-обертки над одним Bot API. aiogram (ветка 3.x) изначально асинхронный; python-telegram-bot — более распространенная универсальная библиотека. Синтаксис разный, поэтому код из статьи на aiogram без переписывания не запустится.

OTUS Журнал
Скидка 5% 14-20 сентября на курсы (popup)