Telegram Bot API позволяет получать сообщения своего бота и отправлять ответы через HTTPS-запросы. В этом практикуме вы вручную заберёте две пробы из личного диалога, ответите на них и подтвердите обработанные события через offset. Программа и сервер для такого опыта не понадобятся.

Штольц, деятельный друг Обломова, подбирал к каждому делу свой приём. В эпиграфе Гончаров сравнивает его с ключницей — хранительницей домашних ключей, которая сразу знает нужный. Нам пригодится та же разборчивость: получить письмо, ответить на него и отметить обработанное — три разных действия. Telegram терпеливо различает их, даже когда человек уже считает всё одним большим «я же ответил».

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

Подготовьте отдельный диалог и помощника по инструкции

Для опыта нужен новый отдельный бот, которого ещё не подключали к программе, конструктору или webhook. Webhook — адрес программы, куда Telegram сам доставляет события. Регистрация и получение токена разобраны в инструкции по BotFather. Токен даёт доступ к боту; здесь он понадобится только у вас на компьютере.

Откройте личный диалог с этим ботом, нажмите Start или отправьте /start. Затем отправьте по одному два сообщения: Проба-А и Проба-Б. Автоматического ответа сейчас нет: отвечать вы будете отдельной командой.

Дальше показан путь для macOS: приложение «Терминал», оболочка Bash и встроенная утилита curl. Терминал — окно, куда вводят команды компьютеру; curl отправит запрос Telegram и покажет его ответ. Устанавливать дополнительные программы для этих шагов не нужно. Понадобится доступ к api.telegram.org из вашей сети.

Если терминал пока незнаком, откройте чат DeepSeek. Для текстового сопровождения начать можно без платной подписки. Вставьте запрос вместе с этой инструкцией:

Хочу разобраться в Telegram Bot API на отдельном своём тестовом боте, без программы и сервера. Сначала уточни мою ОС и оболочку. Затем по этой инструкции давай одно действие: какое окно открыть, куда ввести строку, что должно появиться. Жди моего ответа. Объясняй getMe, chat.id, update_id и offset обычными словами. Код инструкции сохраняй. Токен и личные данные я не передаю; для разбора покажу только обезличенный фрагмент. При ошибке сверяй официальную документацию, если доступен поиск; без поиска попроси её фрагмент. Не предлагай удалять webhook или останавливать чужой процесс.

Чат объясняет шаги по вашим сообщениям. Экран и терминал он сам не видит; в ответ ему можно описать результат или передать очищенную ошибку. Пользуетесь ChatGPT или Claude — тот же запрос подойдёт и там. На другой ОС сначала уточните с помощником соответствующее окно и оболочку: команды ниже рассчитаны на Bash, в PowerShell их не вставляют.

Введите токен и узнайте бота через getMe

На Mac нажмите ⌘ Space, найдите «Терминал» и откройте его. Введите следующую строку и нажмите Enter:

bash

Откроется вложенная оболочка Bash с новым приглашением к вводу. В этом же окне выполняйте дальнейшие команды по одной. Если терминал покажет совет перейти на zsh, для нашего опыта всё равно оставайтесь в запущенной Bash.

Токен введём отдельно от команды. Скопируйте и выполните только следующий блок:

read -rsp 'Токен своего бота: ' BOT_TOKEN

Появится приглашение «Токен своего бота». Вставьте токен из BotFather и нажмите Enter: символов на экране видно не будет. Токен сохранится в переменной BOT_TOKEN этой оболочки, а Bash вернётся к вводу команд. Приглашение может появиться в той же строке. Секрет вводят на приглашение read, сам код остаётся как в статье.

Теперь отдельно выполните следующий блок — printf переведёт вывод на новую строку:

printf '\n'

Первый запрос — getMe, сведения о боте:

printf 'url = "https://api.telegram.org/bot%s/getMe"\n' "$BOT_TOKEN" |
  curl --disable --config - --silent --show-error --max-time 20

В окне должен появиться JSON — текст с именованными полями в фигурных скобках. Найдите ok: значение true означает успешный запрос. Внутри result найдите username и сверьте его с адресом выбранного бота без @. При ошибке или другом имени дальше не переходите. Назначение метода описано в справке getMe.

Длинную форму команды здесь стоит сохранить. printf передаёт адрес с токеном прямо на вход curl через --config -; адрес не приходится вставлять в браузер или писать с секретом в командной строке. --disable первым аргументом отключает чтение файла настроек curl, а --max-time 20 ограничивает ожидание двадцатью секундами. Эти параметры описаны в руководстве curl. Токен остаётся у вас, в чат помощника его не отправляйте.

Проверьте, что входящие не забирает другая программа

Следующий запрос — getWebhookInfo:

printf 'url = "https://api.telegram.org/bot%s/getWebhookInfo"\n' "$BOT_TOKEN" |
  curl --disable --config - --silent --show-error --max-time 20

Нужны ok:true и пустое поле result.url, то есть "url":"". Непустой адрес означает настроенный webhook. На таком боте опыт остановите и создайте отдельного свободного. Удалять webhook ради упражнения не нужно.

Пустой URL ещё не исключает программу, которая сама опрашивает Telegram методом getUpdates. Такой регулярный опрос называют polling. Поэтому мы и начинаем с нового бота. Если дальше появится Conflict, остановитесь: конкурировать с другой программой за входящие нельзя. Совместимость способов получения описана в разделе Getting updates.

Почему прочитанное приходит снова

Теперь отправьте запрос getUpdates — получить накопившиеся события:

printf 'url = "https://api.telegram.org/bot%s/getUpdates"\n' "$BOT_TOKEN" |
  curl --disable --config - --silent --show-error --max-time 20 --data-urlencode "timeout=0" --data-urlencode "limit=100"

Параметр timeout=0 просит вернуть результат сразу, limit=100 задаёт верхний предел числа событий в ответе. Это один ручной запрос. Если вы закрыли терминал, новые обращения сами не продолжатся.

При успехе внутри result будет массив: список событий между квадратными скобками. Найдите ваши /start, Проба-А и Проба-Б. Если список пуст, отправьте боту ещё одну различимую пробу и повторите запрос. Пустая очередь сама по себе ошибкой API не является.

Откройте рядом обычную локальную заметку и выпишите события по одному. У каждого есть update_id; у сообщения найдите ещё message.text, message.chat.id и message.message_id. Точка показывает, где искать: сначала message, внутри него chat, внутри chat — id. Если JSON вытянулся в длинную строку, ищите названия полей поиском по тексту, сохраняя их вложенность.

Теперь выполните ту же команду ещё раз, по-прежнему без offset:

printf 'url = "https://api.telegram.org/bot%s/getUpdates"\n' "$BOT_TOKEN" |
  curl --disable --config - --silent --show-error --max-time 20 --data-urlencode "timeout=0" --data-urlencode "limit=100"

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

Отправьте два ручных ответа через sendMessage

Для отправки нужен адрес диалога — message.chat.id из вашего собственного личного сообщения. from.id обозначает отправителя, message_id — сообщение внутри чата, а update_id — событие в очереди бота. В этой команде требуется именно chat.id.

Скопируйте его без изменения цифр и знака. В терминале выполните строку, затем введите значение на появившееся приглашение и нажмите Enter:

read -rp 'chat.id из своего личного сообщения: ' CHAT_ID

Используйте только свой личный диалог. Публичный username, канал и группа в этом опыте не нужны. Сам ID сохраните локально и скройте, если будете показывать журнал другому человеку.

Сейчас отправим текст в свой диалог. Если curl вернёт сетевую ошибку или тайм-аут, сначала посмотрите в Telegram: сообщение могло дойти. Запишите исход unknown и не повторяйте отправку автоматически. Два одинаковых ответа могут оказаться результатом двух нажатий Enter.

Отправьте первый ответ:

printf 'url = "https://api.telegram.org/bot%s/sendMessage"\n' "$BOT_TOKEN" |
  curl --disable --config - --silent --show-error --max-time 20 --data-urlencode "chat_id=$CHAT_ID" --data-urlencode "text=Получил Пробу-А. Это ручной ответ."

Ожидайте ok:true, сохраните result.message_id исходящего сообщения и откройте свой диалог в Telegram: там должен появиться указанный текст. Метод sendMessage возвращает отправленное сообщение; параметры и результат есть в официальной справке.

После успешного первого ответа отправьте второй:

printf 'url = "https://api.telegram.org/bot%s/sendMessage"\n' "$BOT_TOKEN" |
  curl --disable --config - --silent --show-error --max-time 20 --data-urlencode "chat_id=$CHAT_ID" --data-urlencode "text=Получил Пробу-Б. Это второй ручной ответ."

Снова сверьте JSON и видимый текст. Вы собственноручно сопоставили «Пробу-А» с первым ответом, «Пробу-Б» — со вторым. Метод sendMessage знает только адрес и текст, которые вы ему передали. Сам выбирать ответ по входящему сообщению он не начнёт.

Дополните локальную заметку: время, входящий update_id и текст, свой chat.id, исход sendMessage (ok, error или unknown), исходящий message_id, следующий offset и видимый результат в диалоге. Входящие и исходящие номера держите в разных столбцах: по номеру отправленного ответа подтверждать очередь нельзя. /start отметьте как просмотренное событие без отправки ответа.

Ниже — условный пример журнала с придуманными номерами. В своём журнале замените их фактическими; chat.id в общей копии скрыт.

Время Входящий update_id и текст chat.id sendMessage Исходящий message_id Следующий offset В диалоге
12:00 501, /start скрыт не отправляли — пока не задан /start просмотрен
12:01 502, Проба-А скрыт ok 10 пока не задан первый ответ
12:02 503, Проба-Б скрыт ok 11 504 после обработки всех трёх второй ответ

Подтвердите очередь через offset

В диалоге уже два ответа. Осталась отметка на входящих: успешный sendMessage её не ставит. Просмотрите весь полученный массив, включая /start. Когда каждое событие понятно и обработано, найдите максимальный update_id и прибавьте один. В условном журнале 501, 502, 503 дают 504. У вас будет своё число.

Если остались незнакомые события или отправка unknown, сначала разберите их. offset — отметка «всё до этого номера обработано», поэтому он подтверждает сразу все меньшие номера. Подняв его раньше времени, можно убрать из очереди ещё не выполненную просьбу. Когда журнал готов, введите свой результат:

read -rp 'Следующий offset (максимальный обработанный update_id + 1): ' NEXT_OFFSET

Затем вызовите getUpdates с этим значением:

printf 'url = "https://api.telegram.org/bot%s/getUpdates"\n' "$BOT_TOKEN" |
  curl --disable --config - --silent --show-error --max-time 20 --data-urlencode "offset=$NEXT_OFFSET" --data-urlencode "timeout=0" --data-urlencode "limit=100"

Именно этот вызов подтверждает события с update_id меньше переданного offset. Если новых нет, увидите result:[]. Если за время работы пришли новые сообщения, ответ может содержать их: разберите отдельно. Правило подтверждения дано в описании getUpdates.

Для последней проверки отправьте в Telegram Проба-В. В терминале повторите запрос с тем же сохранённым NEXT_OFFSET:

printf 'url = "https://api.telegram.org/bot%s/getUpdates"\n' "$BOT_TOKEN" |
  curl --disable --config - --silent --show-error --max-time 20 --data-urlencode "offset=$NEXT_OFFSET" --data-urlencode "timeout=0" --data-urlencode "limit=100"

Найдите новое событие с текстом Проба-В и его собственный update_id. Старые подтверждённые события вернуться не должны. Очередь приняла новое письмо и перестала подавать старые: обе половины проверки сошлись. «Проба-В» ещё не подтверждена старым offset; после её обработки понадобилось бы новое значение. Автоматического ответа тоже не будет.

Завершите опыт, очистив переменные и выйдя из вложенной Bash:

unset BOT_TOKEN CHAT_ID NEXT_OFFSET
exit

Если команда вернула ошибку

При ok:false прочитайте error_code и description. Уберите из фрагмента токен, ID и личный текст; покажите помощнику название метода, очищенное описание и ожидаемый результат. Просите разобрать именно этот шаг, сохранив код инструкции.

Unauthorized — повод проверить ввод токена выбранного тестового бота. chat not found или Forbidden — вернуться к своему личному диалогу: отправили ли вы боту первое сообщение и взяли ли message.chat.id из него? При Conflict или непустом webhook остановите упражнение и возьмите отдельного свободного бота.

Тайм-аут sendMessage оставляет исход unknown, пока вы не выяснили, что произошло в диалоге. Отсутствие ответа curl ещё не означает, что Telegram не отправил сообщение. С такой строкой в журнале подтверждение очереди отложите.

Частые вопросы

Где искать документацию Telegram Bot API?

На официальной странице Bot API перечислены методы и поля. Найдите имя метода из команды. Общий принцип запроса и JSON-ответа объясняет вводный урок Telegram. Помощнику можно передать нужный фрагмент справки без токена.

Почему getUpdates присылает одно и то же?

Проверьте update_id: одинаковый номер означает то же событие. Подтвердите уже обработанные события вызовом с offset, который больше их номеров. Само чтение JSON и отправленный ответ очередью не управляют. Повторяющиеся обновления разобраны и в FAQ Telegram.

Можно ли взять бота, который уже работает?

Для этого опыта создайте отдельного. У рабочего входящие уже может забирать программа или webhook. Ручной getUpdates начнёт спорить с программой за события. Выбирать свободного бота нужно до первого запроса к очереди.

Когда бот начнёт отвечать сам?

Когда программа будет получать новые события и выбирать ответ. Если ручной обмен уже понятен и хочется убрать повторение команд, переходите к первому Telegram-боту на Python. Там появится код, который обрабатывает /start и обычный текст.

Сохраните журнал: две пробы, два ручных ответа и подтверждение входящих. Когда понадобится помощник для своих заметок, откройте карту сценария бота и запишите, что должно происходить с фразой «пересадить фикус»: куда сохранить её и какой ответ вернуть. Карта поможет сформулировать будущую работу программы. В «Первопроходцах» боты относятся к более позднему этапу программы; по проектной работе можно задать вопрос. Никита Малов проверяет такие работы лично и даёт правки. В следующем проекте у каждого входящего будет своё дело и понятный момент завершения. Фикусу хватит одного горшка.