Почему при вызове editMessageReplyMarkup из 1С пропадает inline-клавиатура в Telegram и как это исправить?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При разработке чат-ботов Telegram на платформе 1С разработчики часто сталкиваются с задачей динамического обновления кнопок — например, при организации постраничной навигации (пагинации) по каталогу товаров, списку контрагентов или отчетам. Для этого в Telegram Bot API предусмотрен специальный метод editMessageReplyMarkup, который должен обновлять только блок инлайн-кнопок без перезаписи текста самого сообщения.

Однако на практике нередко возникает ситуация: код отрабатывает без явных исключений в 1С, но клавиатура под сообщением не обновляется, а полностью исчезает. Разберем по шагам, почему возникает такое поведение, как исправить исходный код и как организовать взаимодействие с Telegram API по лучшим практикам разработки.

В чем заключается основная причина сбоя

Проанализируем строку формирования HTTP-запроса из исходного примера:


Приемник = "bot" + СтруктураНастроек.token + "/editMessageReplyMarkup?chat_id=" + СтрЗаменить(Формат(СтруктураСообщения.callback_query.message.chat.id, "ЧДЦ=; ЧС=; ЧРГ=."), ".", "")+"&message_id=" + message_id + "&reply_markup=" + КнопкиJsInline;
HTTPЗапрос = Новый HTTPЗапрос(Приемник);
Ответ = HTTPСоединение.Получить(HTTPЗапрос);

Обратим внимание на склейку параметра message_id. По умолчанию платформа 1С при неявном приведении чисел к строке (или при использовании стандартных строковых операций) применяет региональные настройки форматирования. В русской локализации триады разрядов отделяются неразрывным пробелом (символ с кодом 160). Если идентификатор сообщения превышает 999 (например, 14 582), то в строку URL вместо сплошного числа попадает пробел.

В результате веб-сервер Telegram парсит URL только до первого недопустимого символа. Параметры message_id и следующий за ним reply_markup либо искажаются, либо полностью отсекаются. Согласно спецификации Telegram Bot API, если в метод editMessageReplyMarkup не передан валидный объект разметки, API считает, что клавиатуру требуется удалить. В итоге кнопки просто исчезают из сообщения.

Решение проблемы: отключение группировки разрядов

Для исправления ошибки достаточно явно задать формат вывода числа без разделения разрядов с помощью функции Формат() и параметра ЧГ=0 (число групп = 0):


СтрокаMessageID = Формат(message_id, "ЧДЦ=0; ЧГ=0");
СтрокаChatID = Формат(СтруктураСообщения.callback_query.message.chat.id, "ЧДЦ=0; ЧГ=0");

Приемник = "bot" + СтруктураНастроек.token + "/editMessageReplyMarkup?chat_id=" + СтрокаChatID + "&message_id=" + СтрокаMessageID + "&reply_markup=" + КнопкиJsInline;

Параметр ЧДЦ=0 гарантирует отсутствие дробной части, а ЧГ=0 полностью убирает неразрывные пробелы между тысячами. Это стандартный и надежный способ форматирования любых целочисленных идентификаторов в 1С (таких как chat_id, message_id, user_id).

Типичные ошибки в архитектуре взаимодействия и их устранение

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

  1. Передача сложных JSON-структур через GET-запрос.

    В исходном примере JSON-строка клавиатуры подставляется прямо в строку URL через метод Получить(). Символы фигурных скобок, кавычек, двоеточий и кириллический текст без специального экранирования нарушают стандарт HTTP. Если используется метод GET, каждый параметр обязан кодироваться функцией КодироватьСтроку(Значение, СпособКодированияСтроки.КодировкаURL).

  2. Ненадежное получение идентификатора сообщения.

    В исходном коде вызывается функция ПолучитьПоследнийMessageID(...). Это опасный подход: если пользователь нажмет кнопку пагинации в старом сообщении выше по истории диалога, бот отредактирует не то сообщение, на которое нажал пользователь, а самое последнее. Правильнее брать идентификатор непосредственно из входящего вебхука: message_id = СтруктураСообщения.callback_query.message.message_id.

  3. Лимит на длину поля callback_data.

    В строке КнопкиInline.Добавить(Новый Структура("text, callback_data", КнопкаInline.Наименование, ТекстCallback_query)) передается GUID контрагента. Напоминаем, что Telegram накладывает жесткое ограничение: параметр callback_data не может превышать 64 байта в кодировке UTF-8. Стандартный GUID занимает 36 символов, но если к нему добавить длинный префикс действия, Telegram вернет ошибку BUTTON_DATA_INVALID.

  4. Модификация массива внутри цикла.

    Посмотрим на блок очистки массива в исходном коде: вызов МассивКнопокInline.Удалить(...) внутри цикла Для каждого по этому же массиву приводит к сдвигу индексов и пропуску элементов. Для пагинации не следует удалять элементы — гораздо надежнее просто выбирать нужный срез по индексам.

  5. Отсутствие проверки ответов сервера.

    Если код Telegram возвращает ошибку, платформа 1С не выбрасывает исключение при обычном вызове HTTPСоединение.Получить(). Ответ сервера необходимо обязательно разбирать через Ответ.ПолучитьТелоКакСтроку(), где в случае сбоя содержится поле description с точной причиной ошибки.

Рекомендуемый вариант: отправка запроса методом POST с телом JSON

Наиболее надежный и профессиональный способ работы с Telegram Bot API — передача всех параметров в теле POST-запроса в формате JSON. Это полностью исключает проблемы с максимальной длиной URL, кодированием спецсимволов и разделителями чисел. Рассмотрим эталонный пример реализации обновления клавиатуры:


// 1. Извлекаем данные из события нажатия кнопки
MessageID = СтруктураСообщения.callback_query.message.message_id;
ChatID = СтруктураСообщения.callback_query.message.chat.id;
CallbackQueryID = СтруктураСообщения.callback_query.id;

// 2. Обязательно подтверждаем получение callback, чтобы у пользователя погасла анимация ожидания
HTTPЗапросCallback = Новый HTTPЗапрос("bot" + СтруктураНастроек.Токен + "/answerCallbackQuery?callback_query_id=" + CallbackQueryID);
HTTPСоединение.Получить(HTTPЗапросCallback);

// 3. Формируем новую структуру клавиатуры
СтрокиInline = Новый Массив;
Преф = "ААИРК";

// Получаем срез контрагентов для текущей страницы без разрушения исходной коллекции
ВсеКонтрагенты = ПолучитьКонтрагентов(20);
НачальныйИндекс = 10;
КонечныйИндекс = Мин(19, ВсеКонтрагенты.Количество() - 1);

Для Индекс = НачальныйИндекс По КонечныйИндекс Цикл
    ТекущийКонтрагент = ВсеКонтрагенты[Индекс];
    Кнопка = Новый Структура;
    Кнопка.Вставить("text", ТекущийКонтрагент.Наименование);
    // Передаем компактный идентификатор (до 64 байт)
    Кнопка.Вставить("callback_data", Строка(ТекущийКонтрагент.УникальныйИдентификатор()));
    
    СтрокаКнопок = Новый Массив;
    СтрокаКнопок.Добавить(Кнопка);
    СтрокиInline.Добавить(СтрокаКнопок);
КонецЦикла;

// Добавляем строку навигации
КнопкаДалее = Новый Структура("text, callback_data", ">", Преф + "Стр3");
СтрокаНавигации = Новый Массив;
СтрокаНавигации.Добавить(КнопкаДалее);
СтрокиInline.Добавить(СтрокаНавигации);

Клавиатура = Новый Структура("inline_keyboard", СтрокиInline);

// 4. Формируем тело запроса для editMessageReplyMarkup
ПараметрыЗапроса = Новый Структура;
ПараметрыЗапроса.Вставить("chat_id", ChatID);
ПараметрыЗапроса.Вставить("message_id", MessageID);
ПараметрыЗапроса.Вставить("reply_markup", Клавиатура);

ЗаписьJSON = Новый ЗаписьJSON;
ЗаписьJSON.УстановитьСтроку();
ЗаписатьJSON(ЗаписьJSON, ПараметрыЗапроса);
СтрокаТелаJSON = ЗаписьJSON.Закрыть();

// 5. Отправляем POST-запрос
HTTPЗапрос = Новый HTTPЗапрос("bot" + СтруктураНастроек.Токен + "/editMessageReplyMarkup");
HTTPЗапрос.Заголовки.Вставить("Content-Type", "application/json; charset=utf-8");
HTTPЗапрос.УстановитьТелоИзСтроки(СтрокаТелаJSON, КодировкаТекста.UTF8, ИспользованиеByteOrderMark.НеИспользовать);

Ответ = HTTPСоединение.ОтправитьДляОбработки(HTTPЗапрос);

// 6. Проверяем результат выполнения
Если Ответ.КодСостояния <> 200 Тогда
    ТелоОтвета = Ответ.ПолучитьТелоКакСтроку();
    ЗаписьЖурналаРегистрации("TelegramBot.ОшибкаРедактирования", УровеньЖурналаРегистрации.Ошибка,,, ТелоОтвета);
КонецЕсли;

Использование сериализации в JSON средствами объекта ЗаписьJSON автоматически сохраняет числовые типы как числа без форматирования и пробелов, корректно экранирует кавычки и спецсимволы в наименованиях контрагентов, а проверка КодСостояния <> 200 позволяет своевременно увидеть ошибки (например, если разметка не изменилась и сервер вернул 400 Bad Request: message is not modified).

← На главную