---
title: "Реакции, стикеры и живой тон бота"
description: "Стикеры и реакции в telegram-боте на Go: NewSticker по file_id без загрузки, setMessageReaction с ReactionTypeEmoji через generic-запрос, лимиты и тон общения."
course: "Telegram-боты на Go: от эха до полноценного сервиса"
url: "https://golangmaster.ru/courses/telegram-bot/lessons/22-reactions-i-stickery/"
published: "2026-09-19"
updated: "2026-09-25"
minutes: 16
---

# Реакции, стикеры и живой тон бота

*Курс «Telegram-боты на Go: от эха до полноценного сервиса», урок 22 из 25. ~16 минут. Обновлено 25.09.2026. Источник: https://golangmaster.ru/courses/telegram-bot/lessons/22-reactions-i-stickery/*

Реакции и стикеры в telegram-боте — самый дешёвый способ сделать общение живым: одна стикерная кнопка заменяет три строки вежливого текста, а реакция эмодзи подтверждает действие, не порождая лишнего сообщения. В этом уроке отправляем стикеры по file_id через tgbotapi.NewSticker, ставим реакции методом setMessageReaction с типом ReactionTypeEmoji — в базовой библиотеке через generic-запрос — и разбираем границу, за которой эмоции начинают вредить.

Бот из первых уроков курса отвечает на каждое действие текстом: приняли, сохранили, готово, неверная команда. В личном диалоге это терпимо, но добавьте бота в рабочий чат на десяток человек — и лента превращается в кашу: восемь коротких подтверждений на каждое реальное сообщение, и всё важное теряется между ними. Телеграфный ответ на это — эмоции: реакция эмодзи на сообщение подтверждает действие без нового сообщения, а стикер вместо шаблонной фразы экономит внимание и читается мгновенно. 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

```go
func 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

```go
func 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 и проверить всё локально.** file_id добывается за минуту: пересылаете стикер своему боту, в хендлере печатаете update.Message.Sticker.FileID — и сохраняете строку в конфиг. Запуск и проверка требуют локального Go: MakeRequest и Send ходят в сеть, а песочница самоучителя ограничена пакетами fmt, strings, strconv, math, sort, errors и time, поэтому сетевой код в браузере не запустится. Быстрый тест тона: замените одно текстовое подтверждение в своём боте на реакцию и поживите с этим день в рабочем чате — сразу видно, где реакции чистят ленту, а где людям не хватает текста.

### Проверка: стикер по идентификатору

**Вопрос:** Как бот отправляет стикер, который уже есть в Telegram?

- Скачивает файл, затем отправляет байты
- ✅ Передаёт file_id в NewSticker — файл остаётся на серверах Telegram
- Создаёт новый пак через createNewStickerSet
- Отправляет ссылку на стикер обычным сообщением

file_id — ссылка на файл в хранилище Telegram: NewSticker с InputFileString или FileID отправляет стикер без единого байта загрузки. Скачивание не нужно, а ссылка обычным сообщением превратит стикер в картинку без анимации пака.

### Проверка: реакция не встала

**Вопрос:** setMessageReaction вернул ошибку про недопустимую реакцию. Самая вероятная причина?

- Превышен лимит 30 запросов в секунду
- ✅ Эмодзи нет в разрешённом наборе чата или реакции в чате выключены
- Бот не имеет прав администратора в этом чате
- JSON собран правильно, ошибка всегда означает сетевой сбой

Реакция обязана быть из разрешённого списка чата: владелец группы ограничивает набор, и бот связан теми же правилами. Лимит даёт 429 с RetryAfter, а права администратора для реакций не нужны — нужен включённый механизм реакций и валидный эмодзи.

### Реакция до сохранения

```go
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)
                // ответа об ошибке хендлер не отправляет
        }
```

**Что выведет программа?**

- Пользователь увидит ошибку — бот ответит текстом
- ✅ Пользователь увидит реакцию, но задача не сохранится и ошибки не узнает
- Бот упадёт с паникой внутри хендлера
- Реакция не встанет, пока saveTask не вернёт nil

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

### Соберите generic-запрос реакции

Впишите тип параметров, ключи полей и метод запроса.

```go
params := tgbotapi.___{
        "___":    strconv.FormatInt(chatID, 10),
        "message_id": strconv.Itoa(messageID),
        "reaction":   "[{\"type\":\"emoji\",\"emoji\":\"👍\"}]",
}
_, err := bot.___("setMessageReaction", params)
```

Подсказка: Параметры живут в tgbotapi.Params, получатель сообщения — ключ chat_id, а незавёрнутый метод вызывает MakeRequest с именем метода строкой.

Правильные ответы в пропусках: `Params`, `chat_id`, `MakeRequest`

### Соберите ответ стикером на входящий

Расставьте строки: от входящего сообщения до отправки стикера.

Правильный порядок строк:

1. `case update.Message != nil:`
2. `chatID := update.Message.Chat.ID`
3. `sticker := tgbotapi.NewSticker(chatID, tgbotapi.InputFileString(fileID))`
4. `sticker.ReplyToMessageID = update.Message.MessageID`
5. `_, err := bot.Send(sticker)`
6. `log.Println(err)`

### Карточки: реакции и стикеры

- **NewSticker** — Конструктор StickerConfig: chatID и файл-идентификатор. Отправка обычным bot.Send; file_id берётся из пересланного стикера и хранится как строка.
- **setMessageReaction** — Метод Bot API для реакций: chat_id, message_id, reaction — JSON-массив, is_big. В базовом tgbotapi вызывается MakeRequest с Params.
- **ReactionTypeEmoji** — Элемент массива реакций: type emoji и сам эмодзи из разрешённого набора чата. Пустой массив снимает реакцию бота.
- **tgbotapi.Params** — Карта map[string]string для generic-запросов: ключи — имена полей Bot API, значения — строки. Числа конвертируются strconv-ом.
- **Порядок подтверждения** — Сначала успешное завершение действия, потом реакция или ответ. Реакция до дела маскирует ошибки: визуально работает, по факту потеряно.

Маршрут вокруг темы: [урок про медиа и файлы](/courses/telegram-bot/lessons/12-media-i-fajly/) — откуда берётся file_id и как работает передача файлов, ведь стикер живёт тем же механизмом; [урок про ошибки и лимиты](/courses/telegram-bot/lessons/17-oshibki-i-limity/) — 429 и RetryAfter, под которые попадает и setMessageReaction; [урок про рассылки](/courses/telegram-bot/lessons/14-rassylki/) — про темп отправок в десятки чатов. Применить эмоции с пользой: [урок про напоминания](/courses/telegram-bot/lessons/19-napominaniya/) — реакция на принятое напоминание вместо сообщения, [урок про Mini Apps](/courses/telegram-bot/lessons/21-mini-apps-obzor/) — когда интерфейсу тесно в эмодзи и стикерах. Проверить ветки тона тестами поможет [урок про тестирование бота](/courses/telegram-bot/lessons/23-testy-bota/). Полный маршрут — [на странице курса](/courses/telegram-bot/).

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

- Стикер отправляется по 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, а ошибки разбираются по коду и описанию как обычно.
