При разработке чат-ботов 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).
Даже после исправления форматирования чисел в исходном коде остается несколько скрытых уязвимостей, способных вызвать плавающие ошибки в рабочей базе. Разберем их подробнее.
В исходном примере JSON-строка клавиатуры подставляется прямо в строку URL через метод Получить(). Символы фигурных скобок, кавычек, двоеточий и кириллический текст без специального экранирования нарушают стандарт HTTP. Если используется метод GET, каждый параметр обязан кодироваться функцией КодироватьСтроку(Значение, СпособКодированияСтроки.КодировкаURL).
В исходном коде вызывается функция ПолучитьПоследнийMessageID(...). Это опасный подход: если пользователь нажмет кнопку пагинации в старом сообщении выше по истории диалога, бот отредактирует не то сообщение, на которое нажал пользователь, а самое последнее. Правильнее брать идентификатор непосредственно из входящего вебхука: message_id = СтруктураСообщения.callback_query.message.message_id.
В строке КнопкиInline.Добавить(Новый Структура("text, callback_data", КнопкаInline.Наименование, ТекстCallback_query)) передается GUID контрагента. Напоминаем, что Telegram накладывает жесткое ограничение: параметр callback_data не может превышать 64 байта в кодировке UTF-8. Стандартный GUID занимает 36 символов, но если к нему добавить длинный префикс действия, Telegram вернет ошибку BUTTON_DATA_INVALID.
Посмотрим на блок очистки массива в исходном коде: вызов МассивКнопокInline.Удалить(...) внутри цикла Для каждого по этому же массиву приводит к сдвигу индексов и пропуску элементов. Для пагинации не следует удалять элементы — гораздо надежнее просто выбирать нужный срез по индексам.
Если код Telegram возвращает ошибку, платформа 1С не выбрасывает исключение при обычном вызове HTTPСоединение.Получить(). Ответ сервера необходимо обязательно разбирать через Ответ.ПолучитьТелоКакСтроку(), где в случае сбоя содержится поле description с точной причиной ошибки.
Наиболее надежный и профессиональный способ работы с 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).