Перейти к содержимому
ГоМастер

Поиск по урокам

Найдите урок, курс, термин или раздел

Урок 4 из 25 ~18 мин +20 XP

Первый бот: эхо-собеседник

Эхо-бот на Go — классический первый телеграм бот: он повторяет каждое ваше сообщение и отвечает на него реплаем. Уложимся в тридцать строк: GetUpdates, NewMessage, ReplyToMessageID и bot.Send — построчно, с разбором ошибок, на которых ломаются все новички.

Содержание урока · 10 разделов

Эхо-бот — hello world этой области: что человек написал, то бот и вернул, да ещё с цитированием. Схема тривиальная, но проходит через весь конвейер настоящего бота: получить обновление, проверить, что в нём сообщение, достать текст и chat_id, собрать ответ, отправить, обработать ошибку отправки. Каждый элемент этой цепочки в следующих уроках вырастет в отдельную тему, поэтому эхо-бот полезнее, чем кажется: это работающий каркас, к которому дальше прикручиваются команды, клавиатуры и состояния диалога. Именно поэтому первый телеграм бот стоит писать руками, а не копировать: пальцы запоминают порядок проверок лучше глаз. Приготовьте терминал: токен лежит в переменной окружения, модуль из третьего урока собран, библиотека установлена. Запускать будем локально — песочница сайта без сети, а боту она нужна постоянно.

Первый телеграм бот: полный код эхо-бота#

Вот весь эхо-бот. Не пугайтесь объёма: треть здесь — импорты и обработка ошибок, которая в учебных примерах часто выброшена, а в рабочем коде обязательна. Скопируйте в main.go, подставьте переменную окружения с токеном и запустите, затем напишите боту что-нибудь из Telegram — и он ответит цитатой вашего сообщения. Разбор каждой строки идёт сразу после кода, а после него — две правки, которые превратят безликое эхо в живого собеседника.

main.go эхо-бота целиком

GO
package main

import (
        "log"
        "os"

        tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
)

func main() {
        bot, err := tgbotapi.NewBotAPI(os.Getenv("BOT_TOKEN"))
        if err != nil {
                log.Panic(err)
        }

        log.Printf("Авторизован под аккаунтом %s", bot.Self.UserName)

        u := tgbotapi.NewUpdate(0)
        u.Timeout = 60

        updates := bot.GetUpdatesChan(u)

        for update := range updates {
                if update.Message == nil {
                        continue
                }

                msg := tgbotapi.NewMessage(update.Message.Chat.ID, update.Message.Text)
                msg.ReplyToMessageID = update.Message.MessageID

                if _, err := bot.Send(msg); err != nil {
                        log.Println("ошибка отправки:", err)
                }
        }
}

NewBotAPI с токеном из окружения — уже знакомая проверка getMe. NewUpdate(0) и Timeout = 60 описывают опрос, GetUpdatesChan возвращает канал обновлений, а цикл for range живёт, пока процесс не убьют. Внутри: проверка Message на nil, сборка ответа NewMessage из chat_id и текста входящего, реплай через ReplyToMessageID и отправка bot.Send с логированием ошибки. Тридцать строк — и у вас есть программа, живущая в Telegram.

Пройдём по лестнице ещё раз, медленно. u := tgbotapi.NewUpdate(0) создаёт конфиг опроса с offset 0 — отдать всё, что копилось; что делает offset, подробно разберём в пятом уроке. u.Timeout = 60 держит HTTP-запрос открытым до минуты, чтобы не бомбить сервер пустыми запросами. bot.GetUpdatesChan(u) запускает внутри библиотеки горутину и возвращает канал: обновления прилетают в for update := range updates, и этот цикл — сердце бота. Первая проверка в теле — update.Message == nil: не каждое обновление содержит сообщение, у нажатий кнопок и правок текста свои поля; пропускаем такие, иначе паника. Из пришедшего сообщения берём update.Message.Chat.ID — куда отвечать — и update.Message.Text — что повторить. Оба поля нужны обязательно: chat_id без текста даёт пустое сообщение, а текст без chat_id не знает адресата.

Как проверять бота вживую#

Запустили — теперь чек-лист проверки из телефона. Пришлите обычный текст: бот должен ответить цитатой. Пришлите эмодзи и стикер: эмодзи приедет текстом, а стикер — обновлением без поля Text, и эхо-бот отправит пустое сообщение с цитатой — полезное наблюдение, объясняющее, почему в будущих уроках перед использованием Text всегда проверяют на пустоту. Отправьте /start: получите его же эхом, о котором говорили выше. Отредактируйте только что отправленное сообщение: бот промолчит — правки живут в другом поле Update. Зайдите с второго устройства или с десктопа: обновления приходят на все сессии одинаково, бот один и тот же. Заодно понаблюдайте за терминалом: каждая отправка бота — это ваш sendMessage, каждое ваше сообщение — чей-то getUpdates, вернувший Update. Пятнадцать минут таких экспериментов дают больше, чем час чтения о Bot API, и все последующие уроки будем проверять именно так.

Кейс: бот-справка для студенческого чата#

Первый заказ, который приносит эхо-шаблон, почти всегда выглядит одинаково: сделать бота, отвечающего на частые вопросы группы. Студсовет, домовой чат, клуб по интересам — логика одна: пришёл вопрос, бот вернул заготовку. Из такого проекта два урока, которых нет в коде. Первый: пишите лог вопросов в файл или базу с первого дня. Через месяц без логов вы не знаете, что реально спрашивают люди, и расширять бота будете вслепую; с логом — берёте топ-десять вопросов и делаете из них команды и клавиатуры из следующих уроков. Второй: у шаблона нет антиспама, и один шутник завалит бота двадцатью сообщениями в минуту — лимиты Telegram и вежливые паузы разбираются в семнадцатом уроке, но помнить о цене молчания стоит уже здесь. Оба урока стоят ноль строк кода на старте и экономят неделю правок потом.

Персонализация: обращение по имени

GO
reply := "Ты написал: " + update.Message.Text

if update.Message.From != nil {
        reply = update.Message.From.FirstName + ", ты написал: " + update.Message.Text
}

msg := tgbotapi.NewMessage(update.Message.Chat.ID, reply)
msg.ReplyToMessageID = update.Message.MessageID

bot.Send(msg)

update.Message.From — указатель на автора: id, FirstName, UserName. Он тоже бывает nil, например у сообщений из каналов, поэтому проверка перед обращением обязательна. FirstName — то имя, что человек указал в профиле, и короткое обращение заметно оживляет бота: один и тот же текст с именем читается совсем иначе. Похожим образом позже будет строиться и вся логика хендлеров: сначала проверки, потом сборка ответа.

ReplyToMessageID: почему бот цитирует вас#

Поле msg.ReplyToMessageID меняет вид ответа: Telegram рисует его с цитатой исходного сообщения, как обычный реплай в приложении. Технически это число — MessageID того сообщения, на которое отвечаем; ноль или отсутствующее поле дают обычное сообщение без цитаты. MessageID уникален внутри одного чата, но не в пределах аккаунта: у одного и того же сообщения в разных чатах идентификаторы разные, поэтому реплай всегда привязан к паре чат-сообщение. Для эхо-бота цитирование — украшение, для сервисных ботов — инструмент: напоминание со ссылкой на задание, ответ на конкретный вопрос в групповом чате, разбор заявки в потоке. Полный набор способов отправки и связывания сообщений разберём в шестом уроке, а здесь важно привыкнуть к паре NewMessage плюс Send: конфиг собирается, отправляется отдельно.

Чего в эхо-боте сознательно нет#

Эхо-бот на Go наивен, и важно видеть границы наивности. Команд он не различает: /start вернётся вам эхом, потому что это тоже просто текст. Состояний нет: бот не помнит, что говорил минуту назад, и не ведёт диалог. Никаких клавиатур и форматирования: чистый текст туда-обратно. Ошибки отправки пишутся в лог, но повторов и очередей нет. Всё это появится позже: команды — в седьмом уроке, reply-клавиатуры — в восьмом, состояния — в десятом. Сейчас ценен скелет: канал обновлений, проверка nil, сборка ответа, отправка с обработкой ошибки — эта четвёрка повторится в любом боте, который вы напишете на Go, от напоминалки до сервиса рассылок. Если разбор каналов и горутин в коде вызывает вопросы, закройте их по продвинутому курсу — там механика расписана с задачами.

Эхо в личке и в группе: разное поведение#

Запустите эхо-бота в группе — и он, скорее всего, замолчит. Это privacy mode: по умолчанию бот в группе видит только команды, адресованные ему, ответы на свои сообщения и сервисные события; свободный текст проходит мимо. Отключается через /setprivacy в BotFather и пере-добавление бота в чат. Подумайте, нужно ли вам это: эхо в общем чате — шум, каждое сообщение получает дубликат с цитатой, и через день группу захочется спасать. В личке эхо безвредно, там и тестируйте. Второе отличие — адресация: в группе update.Message.From.ID — автор сообщения, а Chat.ID — сама группа, и это разные числа. Отвечаете в тот же чат — берите Chat.ID; запоминаете, кто именно написал, — From.ID. В личном диалоге оба совпадают, поэтому о разнице новички узнают только в группе, самое позднее.

Типичная ошибка: медленная обработка внутри цикла#

Цикл из урока разбирает обновления строго по очереди, и это ловушка. Поставьте внутрь запрос к внешнему API на три секунды — и на время каждого запроса бот перестаёт слушать: очередь копится в буферизованном канале библиотеки, а пользователи видят молчание. Один медленный вызов в час незаметен; пробка начинается, когда ботом пользуются одновременно несколько человек. Простейшие средства уже под рукой: вынести медленную часть в горутину — помня про карту состояния и мьютексы из десятого урока, — или отвечать мгновенной заглушкой, а доделывать в фоне. Правильная архитектура для большой нагрузки — webhook из восемнадцатого урока, где обновления приходят HTTP-запросами и обрабатываются параллельно. Пока трафик учебный, достаточно измерять время обработки: одна строка с time.Since в конце цикла покажет, когда заглушки перестают спасать.

Проверка: тип chat_id

Какой тип у update.Message.Chat.ID?

Проверка: NewMessage против Send

Что делает tgbotapi.NewMessage(chatID, text)?

Что ответит эхо-бот на команду

Прочитайте код и выберите, что он выведет:

for update := range updates {
        if update.Message == nil {
                continue
        }

        msg := tgbotapi.NewMessage(update.Message.Chat.ID, update.Message.Text)
        msg.ReplyToMessageID = update.Message.MessageID

        bot.Send(msg)
}

Что увидит бот после правки сообщения

Прочитайте код и выберите, что он выведет:

for update := range updates {
        if update.Message == nil {
                log.Println("обновление без message")
                continue
        }

        log.Println(update.Message.Text)
}

Соберите отправку ответа

Впишите конструктор сообщения, поле реплая и метод отправки.

msg := tgbotapi.(update.Message.Chat.ID, "готово") msg. = update.Message.MessageID if _, err := bot.(msg); err != nil { log.Println(err) }

Соберите сердце эхо-бота

Расставьте строки цикла обновлений в порядке исполнения: от конфига опроса до отправки.

Строки

Ваша программа

Нажимайте на строки слева, чтобы собрать программу

Эхо-бот работает, и это уже настоящий бот: живые люди пишут — он отвечает, день и ночь. Дальше углубление в механику: пятый урок вскрывает long polling — что происходит между NewUpdate и сообщением в канале, зачем нужен offset и почему Timeout не декорация. После него — отправка сообщений во всей полноте: chat_id, реплаи, копирование чужих сообщений. Полный маршрут курса — на странице курса. Домашнее задание ниже — на запуск и правку: настоящие боты получаются у тех, кто ломает собственный код руками и читает чужие ошибки без страха.

Карточки: анатомия эхо-бота

Термин

GetUpdatesChan

нажмите, чтобы перевернуть
Определение

Метод, запускающий long polling: горутина опрашивает getUpdates и кладёт Update в канал. Читается циклом for range.

1 / 6

Песочница урока

Редактируйте код и запускайте его прямо здесь — интерпретатор работает в браузере, ничего устанавливать не нужно. За каждый запуск — +2 XP.

Вывод

Запустите код, чтобы увидеть вывод.

Коротко: что запомнить

  • Эхо-бот — полный конвейер: получить Update, проверить Message на nil, собрать NewMessage, отправить bot.Send.
  • GetUpdatesChan отдаёт канал обновлений; цикл for range живёт весь срок процесса.
  • update.Message равно nil для кнопок и правок; без проверки бот паникует и умирает.
  • chat_id — int64 и адресат ответа; MessageID уникален внутри чата и нужен для реплая.
  • ReplyToMessageID включает цитирование исходного сообщения; ноль — обычный ответ.
  • Эхо-бот не различает команды и не хранит состояние — это каркас для команд, клавиатур и диалогов следующих уроков.

Домашнее задание

Запустите эхо-бота и поиграйте с ним из Telegram: текст, эмодзи, /start, правка своего сообщения. Затем три правки кода по очереди. Первая: уберите проверку update.Message == nil, отредактируйте старое сообщение в чате и наблюдайте панику в терминале, после чего верните проверку. Вторая: замените эхо на приветствие с FirstName из update.Message.From. Третья: закомментируйте ReplyToMessageID и сравните вид ответов. Каждую правку гоняйте отдельно и фиксируйте эффект в комментарии к коду.

Личные заметкиПусто
Хранятся только в вашем браузере (Telegram · Первый бот: эхо-собеседник)

Вопросы и ответы по уроку

Бот отвечает не на каждое сообщение в группе. Почему?

В группах работает privacy mode: по умолчанию бот видит только команды, адресованные ему, и ответы на свои сообщения. Для эха в группе выключите приватность через /setprivacy в BotFather или проверяйте бота в личном диалоге, где ограничений нет.

Почему в примере ошибка bot.Send проверяется, а в фрагментах нет?

В полной версии эхо-бота проверка есть: if _, err := bot.Send(msg); err != nil { log.Println(err) }. В сокращённых фрагментах её опускают ради читаемости. На проде пропуск проверки превращает молчание бота в необъяснимое — логируйте всегда, хоть одной строкой.

Как остановить бота?

Ctrl+C в терминале: процесс с каналом обновлений убивается сигналом. Неподтверждённые обновления Telegram хранит сутки — после рестарта очередь приедет снова, и сообщения не потеряются. Корректное завершение по сигналу с graceful shutdown разбирается в уроке про деплой.

Термины урока

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

Следующая ступень учебного пути

Prometheus25 уроков~12 чпрактика25 уроков уже доступны

Мониторинг Go с Prometheus: метрики, Grafana, алерты

Курс по метрикам в Go: Counter, Gauge, Histogram, лейблы, PromQL, Grafana-дашборды, алерты и бизнес-метрики. 25 уроков с примерами client_golang.

  • Понимать четыре типа метрик и выбирать правильный
  • Экспортировать метрики из Go через promhttp
  • Инструментировать HTTP-запросы и бизнес-события
  • Писать PromQL-запросы: rate, sum, p99