Чтобы добавить команды боту в Telegram, свяжите их с обработчиками работающей программы, затем задайте список меню через setMyCommands. Проверьте и видимый пункт, и ответ после нажатия.
В меню бота появилась команда /start. Нажимаете её, в переписку уходит сообщение, а дальше тишина. Строку Telegram уже показывает; теперь нужно связать присланный текст с действием программы. Разберём обе части на коротком диалоге, который после названия книги выдаёт основу заметки.
Инструкция рассчитана на собственного работающего Go-бота: он уже получает текстовые сообщения и отправляет ответы через sendMessage. Понадобятся доступ к его коду, Go и терминал с Bash и curl. Создание нового бота разобрано в отдельной инструкции по BotFather. В работающей программе добавим выбор ответа, а сообщения она продолжит получать прежним способом.
Если у вас пока только профиль из BotFather, начните с бота в конструкторе: к нему не нужно пристраивать чужой файл Go. А с уже работающим учебным ботом можно идти вместе с ИИ. Код ниже нужен, чтобы после названия книги получить основу заметки; знать каждую его строку заранее необязательно.
В соседней вкладке откройте DeepSeek, создайте новый чат и вставьте запрос. Тем, кто уже пользуется ChatGPT или Claude, подойдёт их обычный чат. Следом передайте текст статьи.
Хочу, чтобы мой учебный Telegram-бот по /start спрашивал название
книги, выдавал основу заметки и понимал /help и /cancel.
Помоги пройти статью. Сначала выясни, действительно ли у меня уже
есть работающий Go-бот и где лежат его файлы. Если нет, объясни,
с какого более простого шага начать, не предлагай вставлять код вслепую.
Работай по одному действию: что открыть, какой фрагмент найти,
куда добавить код и какой ответ проверить. Жди моего результата.
Перед изменением помоги сохранить копию файлов. Токен, пароли
и весь проект не запрашивай; уточняй, какой небольшой фрагмент нужен,
и напоминай убрать секреты. При ошибке сначала разбери её текст.
Ты не видишь мой экран и не запускаешь программу: это делаю я.
Результат первого разговора с помощником — понятный следующий шаг для вашего бота. Если он просит показать код, передавайте только нужный фрагмент без токена и личных сообщений. Сначала проверьте ответ на /start, потом переходите к меню.
Как добавить команды боту в Telegram: сначала ответ
Возьмём учебный бот для книжных заметок. Пока всё делает один человек в личном тестовом диалоге, без публикаций и рассылок. Готовый проход выглядит так.
Человек: /start
Бот: Как называется книга? Пришлите название одним сообщением. Отмена: /cancel.
Человек: Человек, который сажал деревья
Бот: Книга: «Человек, который сажал деревья».
Главная мысль:
Что хочу проверить:
Что попробую применить:
Это редакторский пример ожидаемого диалога. Бот лишь собирает заготовку для читателя, содержание книги он здесь не пересказывает. Результат пригодится после чтения, когда хочется записать мысль и собственное действие, а пустой лист никак не подсказывает, с чего начать.
Назначим ещё две команды. /help объясняет задачу бота и оставляет текущий ввод открытым. /cancel прекращает ожидание названия. После отмены присланная книга уже не должна превращаться в заметку, иначе команда сработала только на словах.
Подключите обработчик к работающему Go-боту
Обработчиком здесь назовём функцию, которая по входящему сообщению выбирает ответ и меняет состояние разговора. Она помнит только одно: ждём ли мы название книги в этом личном чате.
Сохраните следующий код в commands.go рядом с main.go учебного бота. Пример использует стандартную библиотеку Go; строка package main подходит, если основной файл тоже относится к пакету main.
package main
import (
"strings"
"sync"
"unicode/utf8"
)
// Router хранит незавершённый ввод отдельно для каждого личного чата.
type Router struct {
mu sync.Mutex
waiting map[int64]bool
}
func NewRouter() *Router {
return &Router{waiting: make(map[int64]bool)}
}
func (r *Router) Reply(chatID int64, chatType, text string) string {
if chatType != "private" {
return ""
}
r.mu.Lock()
defer r.mu.Unlock()
text = strings.TrimSpace(text)
fields := strings.Fields(text)
if len(fields) == 0 {
return "Пришлите название книги текстом или выберите /cancel."
}
switch fields[0] {
case "/start":
if len(fields) > 2 || (len(fields) == 2 && fields[1] != "book") {
return "Такой раздел неизвестен. Для книжной заметки выберите /start."
}
r.waiting[chatID] = true
return "Как называется книга? Пришлите название одним сообщением. Отмена: /cancel."
case "/help":
return "Соберу основу книжной заметки. /start начинает ввод, /cancel отменяет его."
case "/cancel":
delete(r.waiting, chatID)
return "Ввод отменён. Чтобы начать новую заметку, выберите /start."
}
if strings.HasPrefix(text, "/") {
return "Такой команды нет. Список действий покажет /help."
}
if !r.waiting[chatID] {
return "Для новой книжной заметки выберите /start."
}
if utf8.RuneCountInString(text) > 120 {
return "Пришлите название длиной до 120 знаков или выберите /cancel."
}
delete(r.waiting, chatID)
return "Книга: «" + text + "».\nГлавная мысль:\nЧто хочу проверить:\nЧто попробую применить:"
}
Создайте router := NewRouter() один раз перед обработкой входящих сообщений. Если создавать его заново для каждого сообщения, после /start ожидание названия потеряется.
В месте, где работающая программа выбирает текст ответа, передайте в router.Reply идентификатор чата, тип чата и текст сообщения. В Bot API это поля message.chat.id, message.chat.type и message.text. Для структуры Go с полями Chat.ID, Chat.Type и Text вызов выглядит так.
reply := router.Reply(message.Chat.ID, message.Chat.Type, message.Text)
Если названия полей в вашем проекте отличаются, подставьте их из существующей структуры сообщения. Непустой reply отправляйте имеющимся sendMessage в тот же chat_id, одним ответом вместо прежнего эха. Пустая строка означает, что для этого сообщения отправки нет. Передавайте текст без parse_mode, чтобы введённое название осталось обычным текстом. Сообщения без поля text этот небольшой сценарий пропускает.
Сначала перезапустите учебный бот обычным для проекта способом и вручную отправьте ему /start, затем название книги. До настройки меню уже должен получиться диалог из примера. Если бот продолжает повторять присланные строки, проверьте точку подключения: старое эхо всё ещё выбирает ответ.
Состояние этого примера хранится в памяти процесса. После его перезапуска незавершённый ввод придётся начать через /start. Готовую заметку бот возвращает сообщением, отдельную базу с книгами он не ведёт.
Задайте список через setMyCommands
Теперь опишем работающие действия в меню. Telegram разрешает задавать список через BotFather или API; здесь выберем API, чтобы затем прочитать сохранённые значения тем же способом. Связь между списком и обработкой описана в официальном разделе о командах.
В папке, из которой будете выполнять запросы, сохраните файл commands.json.
{
"scope": {"type": "all_private_chats"},
"language_code": "",
"commands": [
{"command": "start", "description": "Начать книжную заметку"},
{"command": "help", "description": "Как работает заметка"},
{"command": "cancel", "description": "Отменить ввод названия"}
]
}
В поле command имена записаны без косой черты. Здесь используем область личных чатов all_private_chats и пустой код языка, общий вариант для этой области. Описания оставим по-русски.
Команды ниже выполняются в Bash, в том числе на Mac или Linux с уже доступным curl. Если открыт другой shell, сначала выполните bash. Введите токен своего учебного бота скрытым вводом.
read -r -s -p "Токен учебного бота: " BOT_TOKEN
printf '\n'
Сначала прочитайте прежний список для выбранной области и сохраните ответ в commands-before.json.
printf 'url = "https://api.telegram.org/bot%s/getMyCommands"\n' "$BOT_TOKEN" |
curl --silent --show-error --config - --request POST \
--header 'Content-Type: application/json' \
--data-binary '{"scope":{"type":"all_private_chats"},"language_code":""}' \
--output commands-before.json
cat commands-before.json
Если в result уже есть нужные команды, перенесите их в новый commands.json вместе с нашими. Метод setMyCommands заменяет список для указанной области и языка. Сохранённый файл помогает сверить, какие действия были доступны до замены.
Когда состав готов, отправьте его.
printf 'url = "https://api.telegram.org/bot%s/setMyCommands"\n' "$BOT_TOKEN" |
curl --silent --show-error --config - --request POST \
--header 'Content-Type: application/json' \
--data-binary @commands.json
При успехе ожидается {"ok":true,"result":true}. Если пришло ok:false, прочитайте description и исправьте названную причину. При неизвестном результате сначала запросите текущий список следующим шагом. Токен передаётся curl через стандартный ввод; в адресную строку браузера его вставлять не требуется.
Прочитайте список обратно и выберите команду
Выполните getMyCommands с теми же scope и language_code.
printf 'url = "https://api.telegram.org/bot%s/getMyCommands"\n' "$BOT_TOKEN" |
curl --silent --show-error --config - --request POST \
--header 'Content-Type: application/json' \
--data-binary '{"scope":{"type":"all_private_chats"},"language_code":""}' \
--output commands-after.json
unset BOT_TOKEN
cat commands-after.json
В успешном ответе массив result должен содержать start, help, cancel с нашими описаниями и те прежние команды, которые вы сохранили. Это проверка записи списка. Затем в личном чате с ботом введите /, выберите start из подсказок и пройдите диалог.
Если в тестовом чате показывается другой набор, проверьте приоритет областей и языков. Отдельный список для этого чата или языка может иметь преимущество. Ответ API для одной области сам по себе не подтверждает меню каждого пользователя.
Проверьте весь проход, включая отмену
После выбора команды смотрите на ответ и на следующий шаг разговора. Следующая таблица задаёт ожидаемое поведение нашего учебного обработчика.
| Что отправить в личном чате | Ожидаемый результат | Что этим проверяем |
|---|---|---|
/start, затем название книги |
Вопрос, затем одна основа заметки | Начало и завершение сценария |
/start book, затем название |
Та же основа заметки | Поддержанный параметр старта |
/start, затем снова /start |
Один активный ввод названия | Повтор запуска |
/start, /help, затем название |
Справка, затем заметка | Помощь сохраняет текущий шаг |
/start, /cancel, затем название |
После отмены бот предлагает начать через /start |
Ввод действительно прекращён |
/start secret |
Ответ про неизвестный раздел | Чужой параметр проверяется |
/start, /oops, затем название |
Подсказка к /help, затем заметка |
Неизвестная команда сохраняет ожидание |
Название во втором личном чате без /start |
Предложение начать через /start |
Состояния людей разделены |
Например, после /cancel пришло название «Человек, который сажал деревья», и бот снова выдал заготовку. Значит, ответ об отмене уже отправлен, а состояние ожидания осталось. Исправлять в таком случае нужно обработчик; повторная запись меню этот шаг диалога не изменит.
В нашем коде сообщения из групп пропускаются. Область меню задаёт видимость команд, проверка типа чата остаётся в самой программе. Если позже появятся действия для администратора, права проверяются отдельно при каждом таком действии.
Вопросы и ответы
Хватит ли добавить /start через BotFather?
Для появления подсказки это подходящий путь, для ответа нужен обработчик. После настройки списка отправьте /start вручную. Если сообщение доходит, а ответа нет, ищите его обработку в работающей программе.
Нужно ли снова получать сообщения через getUpdates, чтобы проверить меню?
Для списка достаточно getMyCommands. Существующий бот продолжает получать обновления своим прежним способом. Вторая программа получения обновлений для этой проверки не нужна.
Можно добавить команду по-русски?
Описание может быть русским; имя API-команды использует строчные латинские буквы, цифры и подчёркивание. Поэтому в примере записано cancel, а рядом поясняется действие «Отменить ввод названия».
Что означает параметр book после /start?
Это выбранная нами метка книжного сценария. Её можно прислать вручную как /start book; такой же текст приходит из стартовой ссылки Telegram вида https://t.me/ИМЯ_ВАШЕГО_БОТА?start=book. Подставьте настоящее имя собственного бота. Параметр проверяет программа, произвольная метка сама по себе нового сценария не создаёт.
Рабочее меню готово, когда выбранная из списка команда доводит человека до понятного ответа, а отмена оставляет разговор в ожидаемом состоянии. Сохраните рядом с кодом список команд и таблицу пройденных случаев: при следующем изменении будет видно, какое поведение вы хотели сохранить.
Для следующей версии этого же бота откройте карту сценария и испытаний. В «Что запускает сценарий» перенесите /start и название книги, в «Что получает человек» опишите заметку, а в неверном входе и отказе укажите /oops и /cancel. В «Первопроходцах» можно задавать вопросы по своей работе; проектные работы Никита Малов проверяет лично и даёт правки. Боты относятся к позднему этапу программы, уроки открываются постепенно.
Документация Telegram сверена 18 октября 2026 года. Диалог учебный, обложка создана с помощью ImageGen.