Почему в Telegram-боте из 1С отображается только 8 кнопок и как сделать многострочную клавиатуру?

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

При интеграции 1С с Telegram через Bot API разработчики нередко сталкиваются с ситуацией, когда бот должен отправить пользователю список значений в виде кнопок inline-клавиатуры (InlineKeyboardMarkup). Например, список дней текущего месяца для выбора даты записи или отчета. Однако после формирования JSON и отправки сообщения в чате появляется только 8 кнопок, а остальные числа просто исчезают.

Проанализируем исходный код, разберем причины такого поведения платформы Telegram и рассмотрим правильные варианты реализации многострочной сетки кнопок средствами 1С:Предприятия.

Причина проблемы: архитектура клавиатуры и ограничения Telegram Bot API

Посмотрим на исходный код формирования кнопок:


Функция СгенерироватьДаты(Дата)
    Строки = Новый Массив;
    Кнопки = Новый Массив;
    Для i = День(Дата) По День(КонецМесяца(Дата)) Цикл
        Кнопки.Добавить(Новый Структура("text, callback_data", Строка(i), Строка(i)));
    КонецЦикла;    
    Строки.Добавить(Кнопки);
    // ... сериализация в JSON
КонецФункции

В этом коде все кнопки календаря помещаются в один-единственный массив Кнопки, который затем добавляется в массив Строки. В структуре JSON такой объект представляет собой матрицу (массив массивов), где каждый вложенный массив является отдельным горизонтальным рядом кнопок на экране пользователя.

Выясним причину, почему отображается лишь часть списка. В Telegram Bot API действуют жесткие технические ограничения:

  1. Лимит кнопок в одном ряду: в одном горизонтальном массиве допускается максимум 8 кнопок. Если передать в одном ряду больше кнопок, Telegram либо молча обрезает ряд до 8 элементов, либо отклоняет запрос с ошибкой Bad Request: BUTTON_ROW_TOO_LONG.
  2. Общий лимит клавиатуры: в одном сообщении суммарно не может содержаться более 100 inline-кнопок.
  3. Лимит размера данных: полезная нагрузка параметра callback_data ограничена размером от 1 до 64 байт в кодировке UTF-8.

Таким образом, как верно отмечено участниками обсуждения, клавиатуру необходимо «наращивать вниз, а не вбок», то есть разбивать общий массив кнопок на несколько горизонтальных рядов.

Решение 1: Разбиение кнопок на строки фиксированной длины (чанкование)

Рассмотрим базовое решение: разобьем массив дней на ряды по 7 штук (по числу дней в неделе) или по 4–5 штук, если требуется сделать кнопки крупнее для удобного нажатия с мобильных устройств.

Разберем алгоритм по шагам:

  1. Создаем главный массив СтрокиКлавиатуры, куда будут складываться готовые ряды.
  2. Создаем массив ТекущийРяд для накопления кнопок одной строки.
  3. В цикле добавляем кнопку в ТекущийРяд. Как только его длина достигает заданного предела (например, 7 элементов), добавляем ТекущийРяд в СтрокиКлавиатуры и инициализируем новый пустой массив ТекущийРяд = Новый Массив.
  4. После завершения цикла обязательно проверяем, остались ли «незакрытые» кнопки в переменной ТекущийРяд, и если да — добавляем их в общий массив.

Посмотрим на пример функции в 1С:


Функция СгенерироватьДаты(Дата) Экспорт
    
    СтрокиКлавиатуры = Новый Массив;
    ТекущийРяд = Новый Массив;
    КнопокВРяду = 7;
    
    ДеньНачала = День(Дата);
    ДеньОкончания = День(КонецМесяца(Дата));
    
    Для i = ДеньНачала По ДеньОкончания Цикл
        
        // Кодируем данные без пробелов группировки разрядов
        ТекстКнопки = Формат(i, "ЧГ=0");
        ЗначениеКнопки = Формат(Дата(Год(Дата), Месяц(Дата), i), "ДФ=yyyyMMdd");
        
        Кнопка = Новый Структура("text, callback_data", ТекстКнопки, ЗначениеКнопки);
        ТекущийРяд.Добавить(Кнопка);
        
        Если ТекущийРяд.Количество() = КнопокВРяду Тогда
            СтрокиКлавиатуры.Добавить(ТекущийРяд);
            ТекущийРяд = Новый Массив;
        КонецЕсли;
        
    КонецЦикла;
    
    // Добавляем оставшийся "хвост" кнопок
    Если ТекущийРяд.Количество() > 0 Тогда
        СтрокиКлавиатуры.Добавить(ТекущийРяд);
    КонецЕсли;
    
    // Сериализация в JSON
    ЗаписьJSON = Новый ЗаписьJSON;
    ПараметрыJSON = Новый ПараметрыЗаписиJSON(ПереносСтрокJSON.Нет);
    ЗаписьJSON.УстановитьСтроку(ПараметрыJSON);
    
    Клавиатура = Новый Структура("inline_keyboard", СтрокиКлавиатуры);
    ЗаписатьJSON(ЗаписьJSON, Клавиатура);
    
    Возврат ЗаписьJSON.Закрыть();
    
КонецФункции

Решение 2: Формирование правильной календарной сетки с днями недели

Проанализируем задачу шире: если выводить дни подряд, начиная с понедельника, то число месяца может оказаться не под своим реальным днем недели. Чтобы интерфейс выглядел как полноценный календарь, добавим строку заголовков и выровняем первую неделю месяца пустыми кнопками.

Посмотрим на пример реализации такого календаря:


Функция СгенерироватьКалендарь(Дата) Экспорт
    
    СтрокиКлавиатуры = Новый Массив;
    
    // 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С

Рассмотрим распространенные ошибки, которые могут возникнуть при передаче параметров из 1С в Telegram:

  1. Неразрывные пробелы при использовании Строка(): стандартная функция платформы Строка() при преобразовании чисел больше 999 разделяет разряды неразрывным пробелом (код символа 160). Если передать, например, год через Строка(Год(ТекущаяДата())), 1С вернет "2 026", что нарушит структуру запроса. Для передачи любых числовых параметров в API всегда используйте Формат(Число, "ЧГ=0").
  2. Потеря контекста даты: в исходном коде автора в callback_data передавалось только число: Строка(i). Когда пользователь нажмет кнопку, бот получит лишь номер дня, но не сможет определить, к какому месяцу и году этот день относится. Рекомендуется сразу передавать строку даты формата yyyyMMdd (например, "20260331") — это занимает всего 8 байт и полностью укладывается в 64-байтный лимит Telegram.
  3. Очистка структуры при создании массивов: обратите внимание, что массив ТекущийРяд нужно создавать заново через Новый Массив перед заполнением новой строки, а не очищать методом Очистить(). Если применить Очистить(), то из-за передачи по ссылке очистятся и те строки, которые уже были добавлены в общий массив СтрокиКлавиатуры.

Альтернативные способы реализации

Если вывод большого количества кнопок перегружает мобильный экран, можно использовать альтернативные подходы:

  1. Календарь с постраничной навигацией: добавьте под сеткой кнопок управляющий ряд со стрелками переключения месяцев (««», «»»). При нажатии на стрелку бот должен не отправлять новое сообщение, а вызывать метод Telegram Bot API editMessageReplyMarkup, обновляя кнопки в текущем сообщении «на лету».
  2. Интеграция с Telegram Mini App (Web App): вместо громоздкой сетки inline-кнопок можно отправить пользователю одну кнопку с параметром web_app. По клику откроется встроенное веб-приложение с нативным HTML5-календарем операционной системы. Этот подход снимает любые ограничения на сетку кнопок и обеспечивает удобный интерфейс.
← На главную