Бот из первых уроков курса отвечает на каждое действие текстом: приняли, сохранили, готово, неверная команда. В личном диалоге это терпимо, но добавьте бота в рабочий чат на десяток человек — и лента превращается в кашу: восемь коротких подтверждений на каждое реальное сообщение, и всё важное теряется между ними. Телеграфный ответ на это — эмоции: реакция эмодзи на сообщение подтверждает действие без нового сообщения, а стикер вместо шаблонной фразы экономит внимание и читается мгновенно. Bot API умеет и то и другое, причём механизм реакций появился позже обёртки tgbotapi и вызывается через generic-запрос — это полезный навык сам по себе: API Telegram растёт быстрее любой библиотеки, и умение дёрнуть незавёрнутый метод закрывает половину таких пробелов. Разберём обе механики с кодом, лимитами и двумя историями, где эмоциональный тон бота обернулся против него.
Стикеры: NewSticker и file_id без загрузки#
Стикер для Bot API — это медиа-сообщение, и устроен он так же, как фото из урока про медиа: у каждого стикера есть file_id, по которому Telegram отдаёт файл без всякой загрузки с вашей стороны. Бот отправляет стикер конфигурацией tgbotapi.NewSticker(chatID, file): во второй аргумент передаётся идентификатор — в базовой версии библиотеки это tgbotapi.InputFileString(fileID), в свежих форках то же самое выглядит как tgbotapi.FileID(fileID), и смысл не меняется: путь к файлу на серверах Telegram. Где взять file_id: переслать нужный стикер боту и посмотреть в логе Message.Sticker.FileID — приём тот же, что для фото. Хранится идентификатор как обычная строка в конфиге или базе и живёт долго; у стикеров, в отличие от некоторых временных файлов, идентификатор стабильный. Свой набор боту не нужен: слать можно стикеры из любых общедоступных паков, а создание собственного пака через createNewStickerSet — отдельная тема, для тона бота хватит готовых.
Отправка стикера по file_id
GOfunc answerWithSticker(bot *tgbotapi.BotAPI, chatID int64) {
sticker := tgbotapi.NewSticker(chatID,
tgbotapi.InputFileString("CAACAgIAAxkBAAEB..."))
// тонкая настройка как у любого сообщения:
sticker.ReplyMarkup = tgbotapi.NewReplyKeyboard(
tgbotapi.NewReplyKeyboardRow(
tgbotapi.NewKeyboardButton("Ещё"),
),
)
if _, err := bot.Send(sticker); err != nil {
log.Println("sticker:", err)
}
}StickerConfig ведёт себя как обычное сообщение: та же отправка через bot.Send, тот же ReplyMarkup, тот же chat_id. Ошибка чаще всего означает, что file_id от другого бота — идентификаторы медиа не переносятся между ботами, каждый получает свои. Логирование ошибки обязательно: без него бот молчит, и кажется, что стикер не нравится пользователю, а не библиотеке.
Реакции: setMessageReaction и ReactionTypeEmoji#
Реакция — метод setMessageReaction из Bot API, который базовая обёртка не завершила конфигурацией, поэтому вызывается generic-запросом: bot.MakeRequest с именем метода и картой параметров tgbotapi.Params, где ключи — имена полей API, а значения — строки. Обязательные параметры: chat_id и message_id — на чьё сообщение ставим, и reaction — JSON-массив реакций. Каждый элемент массива — объект типа ReactionTypeEmoji: поле type со значением emoji и поле emoji с самим эмодзи, например огонёк или большой палец. Необязательный is_big делает реакцию крупной, а пустой массив reaction снимает реакцию бота с сообщения. В свежих обёртках и форках библиотеки для этого есть типизированная конфигурация — идея та же, только JSON собирает компилятор, — но схема запроса не менялась: тот же метод, те же поля. Ответ метода — простой True, и ошибка приходит по тем же правилам, что и весь API: код ответа отличен от ожидаемого, в описании причина.
Generic-запрос setMessageReaction
GOfunc react(bot *tgbotapi.BotAPI, chatID int64, messageID int, emoji string) error {
params := tgbotapi.Params{
"chat_id": strconv.FormatInt(chatID, 10),
"message_id": strconv.Itoa(messageID),
"reaction": "[{\"type\":\"emoji\",\"emoji\":\"" + emoji + "\"}]",
"is_big": "false",
}
_, err := bot.MakeRequest("setMessageReaction", params)
return err
}
// использование: подтверждение без нового сообщения
_ = react(bot, update.Message.Chat.ID, update.Message.MessageID, "👍")Params — это map[string]string, поэтому числа конвертируются strconv-ом, а JSON-массив реакций собирается строкой. Внутри строки JSON-кавычки экранированы обратными слэшами — типичная опечатка здесь ломает не компиляцию, а запрос: Telegram ответит ошибкой про невалидное поле. Ответ MakeRequest — готовая структура APIResponse, и err отличен от nil при любом отклонении, включая 429.
Когда реакции уместны, а когда вредят#
Реакция — это подтверждение, и её честное место — в конце цепочки, когда действие уже сделано: задача записана, напоминание поставлено, файл сохранён. Пользователь видит жест на своём сообщении, лента не растёт, диалог остаётся чистым. Второй сценарий, где реакции незаменимы, — групповые чаты: бот подтверждает реакции вместо восьми сообщений принято-принято-принято, и рабочий чат остаётся рабочим. Граница уместности тоже ясная. Реакция не заменяет ответ на ошибку: если команда не сработала, человеку нужен текст — что именно не так и как исправить, а большой палец на невыполненном запросе выглядит насмешкой. Реакция не заменяет диалог: анкеты, уточнения и выбор — это сообщения и клавиатуры. И у эмодзи есть смысловая нагрузка: огонь на сообщении об ошибке читается как издёвка, поэтому набор бота стоит держать узким и предсказуемым — палец для успеха, глаза для принятия к сведению, и всё. Спорная точка курса: часть разработчиков ставит реакции на каждое входящее сообщение ради анимации — по опыту это дешёвый эффект, который быстро обесценивает и реакции, и бота.
Реальный кейс: бот-учёт задач в чате команды ставил галочку на каждом сообщении с префиксом задачи — и в какой-то спринт оказалось, что половина задач не записана. Причина простая: хендлер реагировал до сохранения, а сохранение падало — сначала на редком формате номера, потом на сетевом сбое, — и на эти падения никто не смотрел, потому что визуально всё работало: реакция стоит, все довольны. Правило отсюда: реакция ставится строго после успешного завершения действия, тем же порядком, каким ставится отправка ответа, — и ошибка реакции не глотается молча. Вторая ловушка — темп: бот, поставивший реакции на сотню сообщений в очереди после простоя, ловит 429 с RetryAfter наравне с рассылкой; лимиты Telegram едины для всех методов, и вежливые паузы из уроков про лимиты и рассылки распространяются на setMessageReaction в полной мере.
Лимиты реакций и custom_emoji в чатах#
Пара технических деталей, которые экономят вечер. Набор допустимых эмодзи фиксирован Telegram: реакция ставится только эмодзи из разрешённого списка и только в чатах, где реакции включены на уровне настроек чата — в группе можно ограничить набор, и бот связан теми же ограничениями, что и люди. Попытка поставить странный эмодзи или реакцию в чате с выключенными реакциями вернёт ошибку про недопустимую реакцию — это нормальный ответ API, обрабатывайте его без паники. Тип custom_emoji с анимированными эмодзи существует в схеме реакций, но для ботов привычный и надёжный путь — обычный emoji-набор. Стикеры при этом лимитам послушны точно так же: это обычные медиа-отправки со всеми счётчиками. И тонкое место в группах: бот видит только сообщения, для которых у него есть доступ, и не может ставить реакции на чужие служебные сообщения — если реакция не встаёт, проверьте сначала message_id, а потом разрешения.
Что выбрать под сообщение#
| Инструмент | Когда | Цена и риск |
|---|---|---|
| Текст | Ошибка, инструкция, диалог | Растит ленту; нужен всегда при сбоях |
| Стикер | Празднование результата, дружеский тон | Медийно, но быстро приедается; нужен file_id |
| Реакция | Тихое подтверждение успешного действия | Ноль новых сообщений; 429 при потоке, обязана идти после дела |
| Текст + клавиатура | Выбор и анкеты | FSM и состояния, дольше всего писать |
Пачка стикеров в конфиге бота#
Пару слов про выбор стикеров заранее, потому что это решение конфигурации, а не кода. Готовить пачку file_id стоит в одном месте: константы или конфиг-файл с осмысленными ключами — успех, мягкая ошибка, праздник, — и хендлеры берут стикер по ситуации, а не пишут идентификатор в строке. Тогда смена тона бота — это правка конфига без пересборки, а тестовый прогон подменяет пачку на пустую и проверяет, что бот не падает без стикеров. Набор метода getStickerSet позволяет скачать целый пак одним вызовом и собрать идентификаторы пачкой, но для тона бота хватает пяти-семи стикеров из разных паков. И правило для групп: там стикеры быстрее превращаются в шум, чем реакции, потому что стикер — это целое сообщение; в группах бот подтверждает действия реакциями, а стикеры оставляет личному диалогу, где нет чужой ленты, которую можно засорить.
file_id добывается за минуту: пересылаете стикер своему боту, в хендлере печатаете update.Message.Sticker.FileID — и сохраняете строку в конфиг. Запуск и проверка требуют локального Go: MakeRequest и Send ходят в сеть, а песочница самоучителя ограничена пакетами fmt, strings, strconv, math, sort, errors и time, поэтому сетевой код в браузере не запустится. Быстрый тест тона: замените одно текстовое подтверждение в своём боте на реакцию и поживите с этим день в рабочем чате — сразу видно, где реакции чистят ленту, а где людям не хватает текста.
Проверка: стикер по идентификатору
Как бот отправляет стикер, который уже есть в Telegram?
Проверка: реакция не встала
setMessageReaction вернул ошибку про недопустимую реакцию. Самая вероятная причина?
Реакция до сохранения
Прочитайте код и выберите, что он выведет:
case update.Message != nil:
_ = react(bot, update.Message.Chat.ID,
update.Message.MessageID, "👍")
err := saveTask(update.Message.Text)
if err != nil {
log.Println("save:", err)
// ответа об ошибке хендлер не отправляет
}Соберите generic-запрос реакции
Впишите тип параметров, ключи полей и метод запроса.
Соберите ответ стикером на входящий
Расставьте строки: от входящего сообщения до отправки стикера.
Строки
Ваша программа
Нажимайте на строки слева, чтобы собрать программу
Карточки: реакции и стикеры
Маршрут вокруг темы: урок про медиа и файлы — откуда берётся file_id и как работает передача файлов, ведь стикер живёт тем же механизмом; урок про ошибки и лимиты — 429 и RetryAfter, под которые попадает и setMessageReaction; урок про рассылки — про темп отправок в десятки чатов. Применить эмоции с пользой: урок про напоминания — реакция на принятое напоминание вместо сообщения, урок про Mini Apps — когда интерфейсу тесно в эмодзи и стикерах. Проверить ветки тона тестами поможет урок про тестирование бота. Полный маршрут — на странице курса.
Песочница урока
Редактируйте код и запускайте его прямо здесь — интерпретатор работает в браузере, ничего устанавливать не нужно. За каждый запуск — +2 XP.
Запустите код, чтобы увидеть вывод.
Коротко: что запомнить
- Стикер отправляется по file_id: tgbotapi.NewSticker с InputFileString или FileID — без загрузки байтов и без своего пака.
- Реакция — метод setMessageReaction: в базовой библиотеке через bot.MakeRequest и tgbotapi.Params, reaction — JSON-массив с элементами ReactionTypeEmoji.
- Разрешённый набор эмодзи задаёт чат; недопустимый эмодзи — обычная ошибка API, обрабатываемая без паники.
- Реакция — подтверждение после успешного действия; реакция на ошибку или до сохранения врёт пользователю.
- Поток реакций подчиняется тем же лимитам, что и рассылки: 429 и RetryAfter никуда не делись.
- Тон бота — это редкий набор эмодзи и стикеров по делу; анимация на каждое сообщение обесценивает и то и другое.
Домашнее задание
Добавьте боту два инструмента тона: команду, отвечающую стикером по сохранённому в конфиге file_id, и реакцию-подтверждение после успешного добавления записи — setMessageReaction через MakeRequest с ReactionTypeEmoji, только после сохранения и с логированием ошибки. Затем проверьте поведение в группе с ограниченным набором реакций: поставьте эмодзи вне разрешённого списка и убедитесь, что бот отвечает ошибкой без падения. В конце замените одну текстовую очередь подтверждений на реакции и оцените, насколько чище стала лента чата.
Личные заметкиПусто
Вопросы и ответы по уроку
Может ли бот ставить реакции на любые сообщения?
На те, которые видит, в чатах, где реакции включены: метод требует chat_id и message_id, а набор эмодзи ограничен настройками чата. На служебные сообщения и там, где бот не имеет доступа, реакция вернёт ошибку — это штатный ответ, а не авария.
Стоит ли делать собственный стикерпак для бота?
Для тона — нет: годятся стикеры из любых общедоступных паков по file_id, а создание пака через createNewStickerSet добавляет отдельную тему с правами и обновлениями. Свой пак оправдан, когда стикер несёт бренд или уникальную информацию, которую готовыми паками не передать.
Почему response от MakeRequest называется иначе, чем у Send?
MakeRequest возвращает общую структуру APIResponse без типизированного результата: метод не завершили в библиотеке, поэтому и ответа-типа нет. Для setMessageReaction этого достаточно — метод возвращает True, а ошибки разбираются по коду и описанию как обычно.
Термины урока
Ключевые понятия из этого урока — короткие определения в глоссарии. Наведите на термин, чтобы вспомнить, откройте карточку для подробностей и ссылки на практику.
Изучайте дальше
Следующая ступень учебного пути
Мониторинг Go с Prometheus: метрики, Grafana, алерты
Курс по метрикам в Go: Counter, Gauge, Histogram, лейблы, PromQL, Grafana-дашборды, алерты и бизнес-метрики. 25 уроков с примерами client_golang.
- Понимать четыре типа метрик и выбирать правильный
- Экспортировать метрики из Go через promhttp
- Инструментировать HTTP-запросы и бизнес-события
- Писать PromQL-запросы: rate, sum, p99