Ошибки форматирования Telegram: безопасный HTML для динамического текста
Если фиксированный текст отправляется в Telegram, а после подстановки имени клиента или ответа ИИ запрос отклоняется, сначала проверьте режим форматирования. Для обычного текста не передавайте parse_mode и используйте исходную строку. Для HTML экранируйте динамические значения до вставки в доверенный шаблон. Корректный JSON ещё не означает, что текст соответствует правилам форматирования Telegram.
Главное
- Обычный текст, HTML и MarkdownV2 требуют разных способов подготовки содержимого.
- Храните исходный текст отдельно от результата рендеринга; экранируйте один раз перед отправкой.
- Подтверждённая ошибка формата, ограничение частоты и неизвестный результат отправки — разные ситуации.
- В UnifyPort расширение называется
provider_data.parse_mode, а не передаётся на верхнем уровне как в Bot API.
Сначала изолируйте слой форматирования
Справочник Telegram Bot API описывает parse_mode как режим разбора текстовых сущностей. HTML поддерживает определённый набор тегов, а не произвольную веб-страницу. Документация также различает старый Markdown и MarkdownV2: смена названия режима без смены рендерера проблему не решает.
Проверьте отправку в контролируемый чат на коротком тексте без чувствительных данных:
- Посмотрите окончательный запрос после сериализации SDK или системой автоматизации. Режим может добавляться настройкой по умолчанию.
- Отправьте исходный текст без параметров форматирования. Не используйте строку, уже подготовленную для HTML или Markdown.
- Добавьте простой HTML-шаблон со статическим содержимым.
- Подставьте динамические значения, предварительно экранировав их как HTML-текст.
- Отдельно проверьте ответ API и отображение в приложении.
Если обычный текст тоже не отправляется, изучите фактическую ошибку, адресата, авторизацию и длину сообщения. Не считайте любую проблему ошибкой парсера. Если вызов встроен в ответ webhook, прочитайте сравнение с отдельным запросом sendMessage: встроенный вызов не возвращает доступный для проверки результат отправки.
Экранируйте значения, а не принимайте произвольную разметку
Для HTML Telegram требует заменять символы <, > и &, не являющиеся частью тега или сущности, соответствующими HTML-сущностями. Этот пример JavaScript обрабатывает только текст между тегами. Он не является универсальным HTML-санитайзером, конвертером Markdown или валидатором атрибутов ссылки.
function escapeTelegramHtmlText(value) {
if (typeof value !== 'string') {
throw new TypeError('Expected text');
}
return value
.replaceAll('&', '&')
.replaceAll('<', '<')
.replaceAll('>', '>');
}
function renderSupportAnswer(customerName, answer) {
return `<b>${escapeTelegramHtmlText(customerName)}</b>\n` +
escapeTelegramHtmlText(answer);
}
const rendered = renderSupportAnswer(
'R&D <Team>',
'Use x < 5 & keep the original value.'
);
Амперсанд заменяется первым, чтобы не экранировать сущности, созданные следующими заменами. Ожидаемый исходный HTML:
<b>R&D <Team></b>
Use x < 5 & keep the original value.
Получатель должен увидеть исходные символы, а имя — полужирным. Структурой тегов управляет приложение. Разметка в имени клиента или ответе ИИ должна оставаться содержимым, а не превращаться в инструкции форматирования.
Не экранируйте весь собранный шаблон: иначе доверенные теги станут видимым текстом. Но и не пропускайте экранирование из-за последующего вызова JSON.stringify. Сериализация JSON и разбор HTML в Telegram — разные этапы.
Для ссылок создайте отдельный проверенный построитель с валидацией адреса и безопасной обработкой атрибутов. Здесь динамические атрибуты намеренно не используются. Расширяйте рендерер по документированному набору тегов, а не разрешайте произвольный HTML.
Используйте правильный контракт API
| Интеграция | Поле текста | Параметр форматирования |
|---|---|---|
Официальный Telegram Bot API, sendMessage | text | parse_mode |
| Нормализованная отправка UnifyPort | message.text | provider_data.parse_mode |
Неофициальный интерфейс UnifyPort работает с подключённым аккаунтом мессенджера и использует другой формат запроса. Справочник отправки текста определяет POST /v1/messages, непустое message.text и расширение parse_mode. Матрица возможностей относит это расширение к Telegram: универсальной поддержки HTML во всех каналах она не обещает.
Следующая функция использует рендерер выше. Передайте реальный ID подключённого аккаунта, ID и тип выбранного диалога. Функция только собирает JSON, не отправляет запрос и не выполняет повторные попытки. Это иллюстрация, а не отчёт об интеграционном тестировании.
function buildTelegramReply({ accountId, conversation, name, answer }) {
return {
account_id: accountId,
to: { id: conversation.id, type: conversation.type },
message: {
type: 'text',
text: renderSupportAnswer(name, answer)
},
provider_data: { parse_mode: 'HTML' }
};
}
Отправьте тело через документированный endpoint с серверной авторизацией X-Api-Key. Для обычного текста уберите provider_data.parse_mode и используйте исходный ответ, а не HTML-строку. Не переносите chat_id, entities и другие поля Bot API в нормализованный запрос без документированного соответствия.
Общий воркер для Telegram, LINE и WhatsApp должен выбирать рендерер по провайдеру. Единая маршрутизация не означает единого синтаксиса форматирования.
Проверьте границы содержимого до настройки повторов
Используйте синтетические данные, а не переписку клиентов:
| Тестовый ввод | Что проверить |
|---|---|
Имя с & и угловыми скобками | Исходные символы видны; форматирование задают только доверенные теги |
Буквальная строка & в оригинале | Она остаётся текстом, а не незаметно декодируется |
| Эмодзи, разные языки и переносы строк | Сериализация и рендеринг сохраняют содержимое |
| Пустой ввод или длинный ответ ИИ | Проверки и политика длины срабатывают до отправки |
| Два рендеринга одного оригинала | Каждый начинается с исходника, а не с предыдущего результата |
Длинный текст сначала разделяйте на смысловые части, затем форматируйте каждую отдельно. Произвольное разрезание готовой HTML-строки может оборвать тег или сущность. Сверяйтесь с текущими ограничениями выбранного метода: пример не реализует проверку длины или поддержку rich messages.
Подтверждённое отклонение из-за формата требует исправления содержимого, а не цикла ожидания. Для ограничения частоты используйте руководство по Telegram 429 и очереди отправки. Тайм-аут без ответа оставляет результат неизвестным. Не отправляйте автоматически обычную текстовую замену: исходное сообщение могло уже дойти.
Частые вопросы
Корректный JSON гарантирует корректное форматирование?
Нет. JSON может успешно разбираться, а содержащийся в нём текст — нарушать правила выбранного режима.
Стоит ли при любой ошибке переключаться на MarkdownV2?
Нет. У MarkdownV2 собственные правила экранирования. Выберите режим и используйте подходящий рендерер.
Почему в обычном тексте видны HTML-сущности?
Проверьте, не отправляете ли вы HTML-экранированный результат после удаления режима разбора. Обычный текст нужно формировать из оригинала.
Подходит ли функция для целого HTML-документа?
Нет. Она экранирует только динамический текст между доверенными тегами и не проверяет произвольную разметку, атрибуты, ссылки или длину.
Следующий шаг и источники
Разделите рендереры обычного текста и HTML, затем проверьте контролируемый Telegram-диалог. Для подключённого аккаунта используйте контракт отправки текста UnifyPort.
Источники проверены 2026-10-21:
Превратите интеграцию сообщений в стабильный продуктовый pipeline.
Начните с отправки через единый API, затем возвращайте входящие сообщения в бизнес-систему стандартными событиями.