Чтобы создать меню Telegram-бота, задайте список команд и отдельные переходы между разделами с возвратом. В примере вручную проверим три раздела и кнопку «Назад» в личном чате.
В боте мастерской человек открыл «Материалы», прочитал про бумагу и краску и теперь хочет посмотреть программу занятия. Удобно, когда рядом есть «Назад к разделам», а после возврата снова виден весь выбор. Соберём такое меню из трёх разделов и проверим каждый путь туда и обратно.
Для ручной проверки понадобятся отдельный собственный учебный бот, личный чат и терминал с Bash и curl. Постоянную работу позже выполняет обработчик. Для пути через конструктор есть инструкция без кода.
Начать можно с карты трёх разделов ниже: для неё достаточно своих заметок. Если затем захотите пройти ручное упражнение, откройте DeepSeek в соседней вкладке, вставьте запрос в новый чат, а следующим сообщением — эту статью. Подойдёт и привычный ChatGPT или Claude.
Помоги сделать меню Telegram-бота: программа мастерской,
материалы, подготовка и возврат к разделам. Сначала спроси,
хочу ли я пока составить карту или пройти ручную проверку по статье.
Для карты задавай вопросы о моих заметках по одному.
Для проверки сначала выясни устройство и наличие отдельного бота.
Объясняй одно действие: что открыть, куда вставить и что увидеть.
Если нужен терминал с Bash и curl, начни с его открытия и проверки.
Жди моего результата, при ошибке разбери её до следующего шага.
Токен не запрашивай, его я ввожу только у себя. Личные данные
из ответов я уберу. Ты не видишь экран и не нажимаешь кнопки.
Здесь три дверцы, и у каждой должен быть путь обратно. Проверять их будем по очереди; копировать всю цепочку команд за один раз не нужно.
Как создать меню Telegram-бота: различите два механизма
Возле поля ввода Telegram открывает список команд бота. Под сообщением можно разместить отдельные кнопки разделов. В нашем примере у этих элементов разная работа.
| Элемент | Что делает человек | Что получает бот | Для чего используем |
|---|---|---|---|
| Список команд возле поля ввода | Выбирает /menu |
Сообщение с текстом /menu |
Заново открыть общую карточку |
| Кнопки под карточкой | Нажимает «Материалы» или «Назад к разделам» | Событие callback_query с меткой выбора |
Сменить раздел в том же сообщении |
Так устроены команды и inline-клавиатуры в Telegram. Добавленная в список команда только отправляет боту текст. Ответ на этот текст задаётся отдельно, как и реакция на кнопку раздела.
Выбираем inline-кнопки с callback_data: в чате останется одна карточка, у которой меняются текст и доступные переходы. Различия между обычной клавиатурой, callback- и URL-кнопками подробнее разобраны в статье о кнопках.
Сначала составьте карту разделов
Для примера возьмём мастерскую линогравюры. «Программа» объясняет, что будем делать, «Материалы» — что выдадут, «Подготовка» — что взять с собой.
| Кнопка на главной | Метка callback_data |
Текст открытого раздела | Куда ведёт «Назад к разделам» |
|---|---|---|---|
| Программа | section_program |
Программа. Нарисуем небольшой эскиз, перенесём его на линолеум и сделаем оттиск на бумаге. | На главную, метка home |
| Материалы | section_materials |
Материалы. Линолеум, резцы, валик, краску и бумагу выдадут в мастерской. Готовый оттиск заберёте с собой. | На главную, метка home |
| Подготовка | section_prepare |
Подготовка. Возьмите фартук или одежду, которую можно испачкать. Если есть идея рисунка, сохраните её на телефоне. | На главную, метка home |
Во всех разделах возврат ведёт на общую карточку home. В многоуровневом каталоге потребуется возврат на предыдущий уровень. Надписи можно менять отдельно от служебных меток: например, назвать «Подготовку» «Что взять с собой».
Подготовьте личный диалог
Зарегистрируйте отдельного бота по инструкции для BotFather, без подключённого конструктора, webhook и другой программы получения событий.
В Bash введите токен скрыто. Все следующие команды выполняйте в том же окне:
read -r -s -p 'Токен учебного бота: ' MENU_BOT_TOKEN
printf '\n'
printf 'url = "https://api.telegram.org/bot%s/getWebhookInfo"\n' "$MENU_BOT_TOKEN" |
curl --silent --show-error --max-time 20 --config -
Продолжайте при ok:true и пустом result.url. Заполненный URL означает настроенный webhook; для ручного упражнения выберите другого бота. Пустой URL проверяет только webhook: отсутствие другого получателя событий проверьте по собственным подключениям.
Теперь отправьте боту /start в личном чате и получите событие:
MENU_OFFSET=0
printf 'url = "https://api.telegram.org/bot%s/getUpdates"\n' "$MENU_BOT_TOKEN" |
curl --silent --show-error --max-time 20 --config - --request POST \
--data-urlencode "offset=$MENU_OFFSET" \
--data-urlencode 'limit=1' \
--data-urlencode 'timeout=0' \
--data-urlencode 'allowed_updates=["message","callback_query"]'
В result должно оказаться ваше сообщение /start. Из message.chat.id возьмите адрес диалога, из message.from.id запомните отправителя. Введите оба значения из собственного ответа:
read -r -p 'message.chat.id: ' MENU_CHAT_ID
read -r -p 'message.from.id: ' MENU_OWNER_ID
Через limit=1 получаем по одному событию. Обработав его, запишите update_id и задайте следующий номер:
read -r -p 'Обработанный update_id: ' MENU_UPDATE_ID
MENU_OFFSET=$((MENU_UPDATE_ID + 1))
После каждого события обновляйте номер. Для следующего чтения повторяйте getUpdates со строки printf, с текущим MENU_OFFSET; строку MENU_OFFSET=0 больше не выполняйте. Более ранние сообщения разберите по порядку. Пустой result:[] означает отсутствие новых событий.
Поведение очереди и offset описано в getUpdates. В примерах токен поступает curl через стандартный ввод. После каждого запроса сначала читайте ответ: ok:false и поле description требуют разобраться с причиной. При обрыве отправки сначала посмотрите в свой чат, появилось ли сообщение, и лишь затем решайте вопрос повтора.
Настройте команду входа в меню
Запишем две команды. /start будет первым входом, /menu позволит открыть разделы заново.
printf 'url = "https://api.telegram.org/bot%s/setMyCommands"\n' "$MENU_BOT_TOKEN" |
curl --silent --show-error --max-time 20 --config - --request POST \
--data-urlencode 'commands=[{"command":"start","description":"Начать"},{"command":"menu","description":"Открыть разделы"}]'
В поле command имя записывается без /. При успехе ожидается ok:true и result:true. Теперь явно задайте для своего личного диалога кнопку, которая открывает список команд:
printf 'url = "https://api.telegram.org/bot%s/setChatMenuButton"\n' "$MENU_BOT_TOKEN" |
curl --silent --show-error --max-time 20 --config - --request POST \
--data-urlencode "chat_id=$MENU_CHAT_ID" \
--data-urlencode 'menu_button={"type":"commands"}'
Это два разных действия: setMyCommands задаёт список, setChatMenuButton выбирает назначение кнопки возле поля ввода. Здесь она открывает команды. Для запуска Mini App нужны другой тип кнопки и само приложение.
Выберите /menu в своём чате, получите событие через getUpdates. Сверьте message.text с /menu, проверьте чат и отправителя, обновите offset. Карточку отправим следующим шагом.
Отправьте главную карточку
Сохраните тексты и две клавиатуры. Главная предлагает три раздела, внутри каждого остаётся кнопка возврата:
MENU_HOME_TEXT='Мастерская линогравюры. Выберите, что хотите узнать.'
MENU_PROGRAM_TEXT='Программа. Нарисуем небольшой эскиз, перенесём его на линолеум и сделаем оттиск на бумаге.'
MENU_MATERIALS_TEXT='Материалы. Линолеум, резцы, валик, краску и бумагу выдадут в мастерской. Готовый оттиск заберёте с собой.'
MENU_PREPARE_TEXT='Подготовка. Возьмите фартук или одежду, которую можно испачкать. Если есть идея рисунка, сохраните её на телефоне.'
MENU_HOME_KEYS='{"inline_keyboard":[[{"text":"Программа","callback_data":"section_program"}],[{"text":"Материалы","callback_data":"section_materials"}],[{"text":"Подготовка","callback_data":"section_prepare"}]]}'
MENU_BACK_KEYS='{"inline_keyboard":[[{"text":"Назад к разделам","callback_data":"home"}]]}'
Отправьте главную:
printf 'url = "https://api.telegram.org/bot%s/sendMessage"\n' "$MENU_BOT_TOKEN" |
curl --silent --show-error --max-time 20 --config - --request POST \
--data-urlencode "chat_id=$MENU_CHAT_ID" \
--data-urlencode "text=$MENU_HOME_TEXT" \
--data-urlencode "reply_markup=$MENU_HOME_KEYS"
Ожидаемый вид карточки:
Мастерская линогравюры. Выберите, что хотите узнать.
[Программа]
[Материалы]
[Подготовка]
Запишите result.message_id из успешного ответа рядом с адресом чата. Дальше берите номер из события нажатия, чтобы менять нужную карточку.
Откройте раздел и вернитесь к выбору
Держите под рукой блок getUpdates и следующие запросы: подтверждать нажатие лучше сразу после его получения. Нажмите «Программа» и повторите getUpdates с текущим offset. В полученном событии найдите callback_query и сверьте:
from.idсовпадает с сохранённымMENU_OWNER_ID;message.chat.idсовпадает сMENU_CHAT_ID;message.message_idуказывает на отправленную главную карточку;dataсодержитsection_program.
Поля указаны внутри callback_query: data задаёт выбор, chat.id и message_id — место ответа.
Возьмите из этого события идентификатор нажатия id и номер сообщения:
read -r -p 'callback_query.id: ' MENU_CALLBACK_ID
read -r -p 'callback_query.message.message_id: ' MENU_MESSAGE_ID
printf 'url = "https://api.telegram.org/bot%s/answerCallbackQuery"\n' "$MENU_BOT_TOKEN" |
curl --silent --show-error --max-time 20 --config - --request POST \
--data-urlencode "callback_query_id=$MENU_CALLBACK_ID"
answerCallbackQuery завершает ожидание после нажатия. Текст карточки меняется отдельным запросом. Для полученного section_program задайте:
MENU_TEXT="$MENU_PROGRAM_TEXT"
MENU_KEYS="$MENU_BACK_KEYS"
Замените текст и клавиатуру вместе:
printf 'url = "https://api.telegram.org/bot%s/editMessageText"\n' "$MENU_BOT_TOKEN" |
curl --silent --show-error --max-time 20 --config - --request POST \
--data-urlencode "chat_id=$MENU_CHAT_ID" \
--data-urlencode "message_id=$MENU_MESSAGE_ID" \
--data-urlencode "text=$MENU_TEXT" \
--data-urlencode "reply_markup=$MENU_KEYS"
В той же карточке теперь ожидается:
Программа. Нарисуем небольшой эскиз, перенесём его
на линолеум и сделаем оттиск на бумаге.
[Назад к разделам]
При успешном ответе сверьте текст, кнопку и прежний номер сообщения. Обновите offset по обработанному событию.
Теперь нажмите «Назад к разделам». Ещё раз выполните цепочку: getUpdates, проверка отправителя и сообщения, ввод нового callback_query.id, затем answerCallbackQuery. В data на этот раз должна прийти метка home. Перед тем же запросом editMessageText задайте другие значения:
MENU_TEXT="$MENU_HOME_TEXT"
MENU_KEYS="$MENU_HOME_KEYS"
После редактирования в карточке снова появятся три раздела. Обновите offset по событию возврата. Так завершается первый путь туда и обратно.
Для остальных переходов используйте ту же последовательность. Меняются только выбранные значения:
Полученная метка data |
Что задать перед editMessageText |
|---|---|
section_program |
MENU_TEXT="$MENU_PROGRAM_TEXT" и MENU_KEYS="$MENU_BACK_KEYS" |
section_materials |
MENU_TEXT="$MENU_MATERIALS_TEXT" и MENU_KEYS="$MENU_BACK_KEYS" |
section_prepare |
MENU_TEXT="$MENU_PREPARE_TEXT" и MENU_KEYS="$MENU_BACK_KEYS" |
home |
MENU_TEXT="$MENU_HOME_TEXT" и MENU_KEYS="$MENU_HOME_KEYS" |
Выполните оба присваивания нужной строки отдельно, затем полный editMessageText. Для нового нажатия замените MENU_CALLBACK_ID его новым идентификатором.
Неизвестную метку сначала сопоставьте с картой разделов и фактическим сообщением. По ней переход выполнять нельзя. В учебном диалоге вернитесь к известной карточке, а для будущей программы запишите такую ситуацию отдельным случаем обработки.
Проверьте весь маршрут
Пройдите все разделы с возвратом и сверьте тексты с таблицей: материалы должны рассказывать о краске и бумаге, подготовка — о фартуке и рисунке.
Успешный проход выглядит так:
- «Программа» открывает программу и возвращает к трём кнопкам.
- «Материалы» открывают список того, что выдадут, и возвращают к тому же выбору.
- «Подготовка» открывает список того, что взять, и тоже возвращает на главную.
- При переходах внутри карточки сохраняется её
message_id, меняются текст и клавиатура. - Команда
/menuзаново открывает главную карточку. Для ручной проверки получите это сообщение черезgetUpdates, сверьте чат и отправителя и повторите блокsendMessage. У новой карточки будет новый номер.
Получив /menu или /start, вы вручную повторяете отправку новой главной. Callback тоже обрабатываете вручную: подтверждаете нажатие и меняете нажатую карточку. В будущей программе сохраните это различие; закрытие терминала завершает наше упражнение.
Вопросы и ответы
Можно настроить меню только через BotFather?
Через BotFather можно задать список команд. Для этого служит /setcommands, как указано в справке Telegram. Ответы на команды и переходы между разделами потребуют своей логики. Для нашего маршрута отдельно нужны приём callback_query и смена карточки.
Почему после выбора команды бот молчит?
У команды в списке ещё должен появиться обработчик ответа. Проверьте message.text в getUpdates; если там есть /menu, после проверки чата и отправителя выполните блок sendMessage. В постоянном боте этот шаг выполняет программа.
Кнопка нажалась, а карточка осталась прежней. Что проверить?
Проверьте результат editMessageText. Успешное подтверждение через answerCallbackQuery снимает ожидание, содержание меняет следующий запрос. Сверьте chat_id, message_id, метку выбора и подготовленные MENU_TEXT и MENU_KEYS. Оба действия разобраны в официальном учебнике, раздел Navigation.
Почему продолжается индикатор ожидания?
Сначала проверьте ответ answerCallbackQuery и соответствие идентификатора свежему событию. Если запрос просрочен, запишите это событие как завершённое с ошибкой и обновите offset по его update_id. Затем нажмите кнопку заново, получите новое событие и подтвердите его новый id. Для следующей попытки заранее подготовьте команды, чтобы сократить паузу после нажатия.
«Назад» можно сделать одной и той же кнопкой везде?
Для нашего меню можно: все разделы возвращаются на общий экран home. В многоуровневом меню заранее определите, куда ведёт каждый возврат. Надпись «Назад» сама по себе адреса перехода не задаёт.
Будет ли меню работать после закрытия терминала?
Карточки останутся в чате, но обработку следующих команд и callback должен выполнять работающий обработчик. Здесь все запросы отправляются вручную. Для постоянного бота маршрут нужно реализовать в программе или подходящем конструкторе.
Перенесите в карту собственные разделы
Замените учебные тексты своей задачей и повторите пути туда и обратно. Возле каждой кнопки запишите, что человек увидит и как вернётся к выбору.
С этой картой откройте практику сценария бота. В «Что запускает сценарий» перенесите /menu и метки разделов, в «Что получает человек» укажите тексты карточек и возврат, а в «Неверный вход и отказ» внесите неизвестную метку и повторное нажатие. В «Первопроходцах» можно задать вопрос по собственной работе; проектные работы Никита Малов проверяет лично и даёт правки. Боты относятся к более позднему этапу программы, до которого участник проходит основы и предыдущие задания. Для разбора сохраните конкретный путь, на котором ожидаемый экран разошёлся с полученным.
Официальные справки Telegram сверены 20 октября 2026 года. Мастерская, тексты разделов и показанный вид карточек составлены для учебного примера. Обложка создана с помощью ImageGen.