При интеграции 1С с Telegram через Bot API разработчики нередко сталкиваются с ситуацией, когда бот должен отправить пользователю список значений в виде кнопок inline-клавиатуры (InlineKeyboardMarkup). Например, список дней текущего месяца для выбора даты записи или отчета. Однако после формирования JSON и отправки сообщения в чате появляется только 8 кнопок, а остальные числа просто исчезают.
Проанализируем исходный код, разберем причины такого поведения платформы Telegram и рассмотрим правильные варианты реализации многострочной сетки кнопок средствами 1С:Предприятия.
Посмотрим на исходный код формирования кнопок:
Функция СгенерироватьДаты(Дата)
Строки = Новый Массив;
Кнопки = Новый Массив;
Для i = День(Дата) По День(КонецМесяца(Дата)) Цикл
Кнопки.Добавить(Новый Структура("text, callback_data", Строка(i), Строка(i)));
КонецЦикла;
Строки.Добавить(Кнопки);
// ... сериализация в JSON
КонецФункции
В этом коде все кнопки календаря помещаются в один-единственный массив Кнопки, который затем добавляется в массив Строки. В структуре JSON такой объект представляет собой матрицу (массив массивов), где каждый вложенный массив является отдельным горизонтальным рядом кнопок на экране пользователя.
Выясним причину, почему отображается лишь часть списка. В Telegram Bot API действуют жесткие технические ограничения:
Bad Request: BUTTON_ROW_TOO_LONG.callback_data ограничена размером от 1 до 64 байт в кодировке UTF-8.Таким образом, как верно отмечено участниками обсуждения, клавиатуру необходимо «наращивать вниз, а не вбок», то есть разбивать общий массив кнопок на несколько горизонтальных рядов.
Рассмотрим базовое решение: разобьем массив дней на ряды по 7 штук (по числу дней в неделе) или по 4–5 штук, если требуется сделать кнопки крупнее для удобного нажатия с мобильных устройств.
Разберем алгоритм по шагам:
СтрокиКлавиатуры, куда будут складываться готовые ряды.ТекущийРяд для накопления кнопок одной строки.ТекущийРяд. Как только его длина достигает заданного предела (например, 7 элементов), добавляем ТекущийРяд в СтрокиКлавиатуры и инициализируем новый пустой массив ТекущийРяд = Новый Массив.ТекущийРяд, и если да — добавляем их в общий массив.Посмотрим на пример функции в 1С:
Функция СгенерироватьДаты(Дата) Экспорт
СтрокиКлавиатуры = Новый Массив;
ТекущийРяд = Новый Массив;
КнопокВРяду = 7;
ДеньНачала = День(Дата);
ДеньОкончания = День(КонецМесяца(Дата));
Для i = ДеньНачала По ДеньОкончания Цикл
// Кодируем данные без пробелов группировки разрядов
ТекстКнопки = Формат(i, "ЧГ=0");
ЗначениеКнопки = Формат(Дата(Год(Дата), Месяц(Дата), i), "ДФ=yyyyMMdd");
Кнопка = Новый Структура("text, callback_data", ТекстКнопки, ЗначениеКнопки);
ТекущийРяд.Добавить(Кнопка);
Если ТекущийРяд.Количество() = КнопокВРяду Тогда
СтрокиКлавиатуры.Добавить(ТекущийРяд);
ТекущийРяд = Новый Массив;
КонецЕсли;
КонецЦикла;
// Добавляем оставшийся "хвост" кнопок
Если ТекущийРяд.Количество() > 0 Тогда
СтрокиКлавиатуры.Добавить(ТекущийРяд);
КонецЕсли;
// Сериализация в JSON
ЗаписьJSON = Новый ЗаписьJSON;
ПараметрыJSON = Новый ПараметрыЗаписиJSON(ПереносСтрокJSON.Нет);
ЗаписьJSON.УстановитьСтроку(ПараметрыJSON);
Клавиатура = Новый Структура("inline_keyboard", СтрокиКлавиатуры);
ЗаписатьJSON(ЗаписьJSON, Клавиатура);
Возврат ЗаписьJSON.Закрыть();
КонецФункции
Проанализируем задачу шире: если выводить дни подряд, начиная с понедельника, то число месяца может оказаться не под своим реальным днем недели. Чтобы интерфейс выглядел как полноценный календарь, добавим строку заголовков и выровняем первую неделю месяца пустыми кнопками.
Посмотрим на пример реализации такого календаря:
Функция СгенерироватьКалендарь(Дата) Экспорт
СтрокиКлавиатуры = Новый Массив;
// 1. Добавляем заголовки дней недели
РядЗаголовков = Новый Массив;
ДниНедели = Новый Массив;
ДниНедели.Добавить("Пн");
ДниНедели.Добавить("Вт");
ДниНедели.Добавить("Ср");
ДниНедели.Добавить("Чт");
ДниНедели.Добавить("Пт");
ДниНедели.Добавить("Сб");
ДниНедели.Добавить("Вс");
Для Каждого ДеньТек Из ДниНедели Цикл
РядЗаголовков.Добавить(Новый Структура("text, callback_data", ДеньТек, "ignore"));
КонецЦикла;
СтрокиКлавиатуры.Добавить(РядЗаголовков);
// 2. Определяем день недели первого числа выбранного месяца
ПервыйДеньМесяца = НачалоМесяца(Дата);
НомерДняНедели = ДеньНедели(ПервыйДеньМесяца); // 1 - Пн, 7 - Вс
ТекущийРяд = Новый Массив;
// Заполняем пропуски до первого дня пустыми неактивными кнопками
Для i = 1 По НомерДняНедели - 1 Цикл
ТекущийРяд.Добавить(Новый Структура("text, callback_data", " ", "ignore"));
КонецЦикла;
// 3. Заполняем дни месяца
ДеньОкончания = День(КонецМесяца(Дата));
Для i = 1 По ДеньОкончания Цикл
ТекстДня = Формат(i, "ЧГ=0");
ЗначениеДня = Формат(Дата(Год(Дата), Месяц(Дата), i), "ДФ=yyyy-MM-dd");
ТекущийРяд.Добавить(Новый Структура("text, callback_data", ТекстДня, ЗначениеДня));
Если ТекущийРяд.Количество() = 7 Тогда
СтрокиКлавиатуры.Добавить(ТекущийРяд);
ТекущийРяд = Новый Массив;
КонецЕсли;
КонецЦикла;
// Заполняем оставшиеся клетки последней недели пустыми кнопками до 7 штук
Если ТекущийРяд.Количество() > 0 Тогда
Пока ТекущийРяд.Количество() < 7 Цикл
ТекущийРяд.Добавить(Новый Структура("text, callback_data", " ", "ignore"));
КонецЦикла;
СтрокиКлавиатуры.Добавить(ТекущийРяд);
КонецЕсли;
// 4. Сериализация в JSON
ЗаписьJSON = Новый ЗаписьJSON;
ЗаписьJSON.УстановитьСтроку();
Клавиатура = Новый Структура("inline_keyboard", СтрокиКлавиатуры);
ЗаписатьJSON(ЗаписьJSON, Клавиатура);
Возврат ЗаписьJSON.Закрыть();
КонецФункции
Рассмотрим распространенные ошибки, которые могут возникнуть при передаче параметров из 1С в Telegram:
Строка(): стандартная функция платформы Строка() при преобразовании чисел больше 999 разделяет разряды неразрывным пробелом (код символа 160). Если передать, например, год через Строка(Год(ТекущаяДата())), 1С вернет "2 026", что нарушит структуру запроса. Для передачи любых числовых параметров в API всегда используйте Формат(Число, "ЧГ=0").callback_data передавалось только число: Строка(i). Когда пользователь нажмет кнопку, бот получит лишь номер дня, но не сможет определить, к какому месяцу и году этот день относится. Рекомендуется сразу передавать строку даты формата yyyyMMdd (например, "20260331") — это занимает всего 8 байт и полностью укладывается в 64-байтный лимит Telegram.ТекущийРяд нужно создавать заново через Новый Массив перед заполнением новой строки, а не очищать методом Очистить(). Если применить Очистить(), то из-за передачи по ссылке очистятся и те строки, которые уже были добавлены в общий массив СтрокиКлавиатуры.Если вывод большого количества кнопок перегружает мобильный экран, можно использовать альтернативные подходы:
editMessageReplyMarkup, обновляя кнопки в текущем сообщении «на лету».web_app. По клику откроется встроенное веб-приложение с нативным HTML5-календарем операционной системы. Этот подход снимает любые ограничения на сетку кнопок и обеспечивает удобный интерфейс.