← Все статьи
Руководство

Ошибки форматирования 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: смена названия режима без смены рендерера проблему не решает.

Проверьте отправку в контролируемый чат на коротком тексте без чувствительных данных:

  1. Посмотрите окончательный запрос после сериализации SDK или системой автоматизации. Режим может добавляться настройкой по умолчанию.
  2. Отправьте исходный текст без параметров форматирования. Не используйте строку, уже подготовленную для HTML или Markdown.
  3. Добавьте простой HTML-шаблон со статическим содержимым.
  4. Подставьте динамические значения, предварительно экранировав их как HTML-текст.
  5. Отдельно проверьте ответ API и отображение в приложении.

Если обычный текст тоже не отправляется, изучите фактическую ошибку, адресата, авторизацию и длину сообщения. Не считайте любую проблему ошибкой парсера. Если вызов встроен в ответ webhook, прочитайте сравнение с отдельным запросом sendMessage: встроенный вызов не возвращает доступный для проверки результат отправки.

Экранируйте значения, а не принимайте произвольную разметку

Для HTML Telegram требует заменять символы <, > и &, не являющиеся частью тега или сущности, соответствующими HTML-сущностями. Этот пример JavaScript обрабатывает только текст между тегами. Он не является универсальным HTML-санитайзером, конвертером Markdown или валидатором атрибутов ссылки.

function escapeTelegramHtmlText(value) {
  if (typeof value !== 'string') {
    throw new TypeError('Expected text');
  }
  return value
    .replaceAll('&', '&amp;')
    .replaceAll('<', '&lt;')
    .replaceAll('>', '&gt;');
}

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&amp;D &lt;Team&gt;</b>
Use x &lt; 5 &amp; keep the original value.

Получатель должен увидеть исходные символы, а имя — полужирным. Структурой тегов управляет приложение. Разметка в имени клиента или ответе ИИ должна оставаться содержимым, а не превращаться в инструкции форматирования.

Не экранируйте весь собранный шаблон: иначе доверенные теги станут видимым текстом. Но и не пропускайте экранирование из-за последующего вызова JSON.stringify. Сериализация JSON и разбор HTML в Telegram — разные этапы.

Для ссылок создайте отдельный проверенный построитель с валидацией адреса и безопасной обработкой атрибутов. Здесь динамические атрибуты намеренно не используются. Расширяйте рендерер по документированному набору тегов, а не разрешайте произвольный HTML.

Используйте правильный контракт API

ИнтеграцияПоле текстаПараметр форматирования
Официальный Telegram Bot API, sendMessagetextparse_mode
Нормализованная отправка UnifyPortmessage.textprovider_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 должен выбирать рендерер по провайдеру. Единая маршрутизация не означает единого синтаксиса форматирования.

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

Используйте синтетические данные, а не переписку клиентов:

Тестовый вводЧто проверить
Имя с & и угловыми скобкамиИсходные символы видны; форматирование задают только доверенные теги
Буквальная строка &amp; в оригиналеОна остаётся текстом, а не незаметно декодируется
Эмодзи, разные языки и переносы строкСериализация и рендеринг сохраняют содержимое
Пустой ввод или длинный ответ ИИПроверки и политика длины срабатывают до отправки
Два рендеринга одного оригиналаКаждый начинается с исходника, а не с предыдущего результата

Длинный текст сначала разделяйте на смысловые части, затем форматируйте каждую отдельно. Произвольное разрезание готовой HTML-строки может оборвать тег или сущность. Сверяйтесь с текущими ограничениями выбранного метода: пример не реализует проверку длины или поддержку rich messages.

Подтверждённое отклонение из-за формата требует исправления содержимого, а не цикла ожидания. Для ограничения частоты используйте руководство по Telegram 429 и очереди отправки. Тайм-аут без ответа оставляет результат неизвестным. Не отправляйте автоматически обычную текстовую замену: исходное сообщение могло уже дойти.

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

Корректный JSON гарантирует корректное форматирование?

Нет. JSON может успешно разбираться, а содержащийся в нём текст — нарушать правила выбранного режима.

Стоит ли при любой ошибке переключаться на MarkdownV2?

Нет. У MarkdownV2 собственные правила экранирования. Выберите режим и используйте подходящий рендерер.

Почему в обычном тексте видны HTML-сущности?

Проверьте, не отправляете ли вы HTML-экранированный результат после удаления режима разбора. Обычный текст нужно формировать из оригинала.

Подходит ли функция для целого HTML-документа?

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

Следующий шаг и источники

Разделите рендереры обычного текста и HTML, затем проверьте контролируемый Telegram-диалог. Для подключённого аккаунта используйте контракт отправки текста UnifyPort.

Источники проверены 2026-10-21:

UnifyPort API

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

Начните с отправки через единый API, затем возвращайте входящие сообщения в бизнес-систему стандартными событиями.