ReplyKeyboardBuilder

Билдер для построения reply клавиатуры. Данный билдер наследуется от базового класса KeyboardBuilderBase.

using Telegram.Bot.Types;
using Telegram.Bot.Types.ReplyMarkups;

namespace PRTelegramBot.Builders.Keyboard
{
    /// <summary>
    /// Билдер для удобного построения ReplyKeyboardMarkup.
    /// Позволяет задавать параметры клавиатуры и динамически добавлять кнопки и строки.
    /// </summary>
    public class ReplyKeyboardBuilder : KeyboardBuilderBase<KeyboardButton, ReplyKeyboardMarkup, ReplyKeyboardBuilder>
    {
        #region Поля и свойства 

        /// <summary>
        /// Запрашивает у клиентов Telegram всегда показывать
        /// пользовательскую клавиатуру, даже если обычная системная клавиатура скрыта.
        /// По умолчанию: false.  
        /// Если false — пользовательская клавиатура может быть скрыта и открыта иконкой клавиатуры.
        /// </summary>
        private bool isPersistent;

        /// <summary>
        /// Запрашивает у клиентов автоматически изменять высоту
        /// клавиатуры для оптимального отображения (например, уменьшить высоту, если всего
        /// две строки кнопок).  
        /// По умолчанию: false.  
        /// Если false — клавиатура всегда отображается той же высоты, что и стандартная.
        /// </summary>
        private bool resizeKeyboard;

        /// <summary>
        /// Запрашивает у клиентов скрывать клавиатуру сразу после того,
        /// как пользователь нажал на кнопку.  
        /// Клавиатура остаётся доступной, но Telegram автоматически переключится на обычную клавиатуру,
        /// и пользователь сможет повторно открыть кастомную клавиатуру кнопкой в поле ввода.
        /// По умолчанию: false.
        /// </summary>
        private bool oneTimeKeyboard;

        /// <summary>
        /// Текст-заполнитель, отображаемый в поле ввода, пока клавиатура
        /// активна. Может содержать от 1 до 64 символов.
        /// </summary>
        private string? inputFieldPlaceholder;

        /// <summary>
        /// Используется, если нужно показать клавиатуру только
        /// определённым пользователям.  
        /// Клавиатура будет видна:
        /// 1) Пользователям, упомянутым в тексте сообщения (@username).  
        /// 2) Если сообщение — ответ, то отправителю оригинального сообщения в этом же чате/треде.
        /// </summary>
        private bool selective;

        /// <summary>
        /// Название кнопки главного меню. Если не указано, кнопки нет.
        /// </summary>
        private string? mainMenuButton;

        /// <summary>
        /// Позиция кнопки главного меню, если кнопка есть.
        /// </summary>
        private MainMenuButtonPosition mainMenuButtonPosition;

        #endregion

        #region Методы

        /// <summary>
        /// Установить флаг постоянной клавиатуры.
        /// </summary>
        public ReplyKeyboardBuilder SetPersistent(bool value = true)
        {
            this.isPersistent = value;
            return this;
        }

        /// <summary>
        /// Установить флаг изменения размера клавиатуры.
        /// </summary>
        public ReplyKeyboardBuilder SetResizeKeyboard(bool value = true)
        {
            this.resizeKeyboard = value;
            return this;
        }

        /// <summary>
        /// Установить флаг временной клавиатуры.
        /// </summary>
        public ReplyKeyboardBuilder SetOneTimeKeyboard(bool value = true)
        {
            this.oneTimeKeyboard = value;
            return this;
        }

        /// <summary>
        /// Установить текст-подсказку в поле ввода.
        /// </summary>
        public ReplyKeyboardBuilder SetInputFieldPlaceholder(string placeholder)
        {
            this.inputFieldPlaceholder = placeholder;
            return this;
        }

        /// <summary>
        /// Показ клавиатуры только определённым пользователям.
        /// </summary>
        public ReplyKeyboardBuilder SetSelective(bool value = true)
        {
            this.selective = value;
            return this;
        }

        /// <summary>
        /// Устанавливает название кнопки главного меню и позицию,
        /// в которой она будет добавлена (сверху или снизу клавиатуры).
        /// Если название не указано — кнопка не будет добавлена.
        /// </summary>
        /// <param name="buttonName">Текст кнопки главного меню.</param>
        /// <param name="mainMenuButtonPosition">Позиция кнопки в клавиатуре (по умолчанию — Bottom).</param>
        /// <returns>Текущий экземпляр билдера.</returns>
        public ReplyKeyboardBuilder SetMainMenuButton(string buttonName, MainMenuButtonPosition mainMenuButtonPosition = MainMenuButtonPosition.Bottom)
        {
            this.mainMenuButton = buttonName;
            this.mainMenuButtonPosition = mainMenuButtonPosition;
            return this;
        }

        /// <summary>
        /// Добавляет обычную кнопку с указанным текстом.
        /// Можно указать, должна ли кнопка быть добавлена в новую строку.
        /// </summary>
        /// <param name="buttonName">Текст кнопки.</param>
        /// <param name="newRow">Если true — кнопка добавляется в новую строку.</param>
        /// <returns>Текущий экземпляр билдера.</returns>
        public ReplyKeyboardBuilder AddButton(string buttonName, bool newRow = false)
        {
            this.AddButton(new KeyboardButton(buttonName), newRow);
            return this;
        }

        /// <summary>
        /// Добавляет кнопку, открывающую WebApp по ссылке.
        /// </summary>
        /// <param name="buttonName">Текст кнопки.</param>
        /// <param name="url">URL WebApp.</param>
        /// <param name="newRow">Если true — кнопка добавляется в новую строку.</param>
        /// <returns>Текущий экземпляр билдера.</returns>
        public ReplyKeyboardBuilder AddButtonWebApp(string buttonName, string url, bool newRow = false)
        {
            this.AddButton(KeyboardButton.WithWebApp(buttonName, new WebAppInfo() { Url = url }), newRow);
            return this;
        }

        /// <summary>
        /// Добавляет кнопку запроса контакта пользователя.
        /// При нажатии Telegram отправит контакт пользователя.
        /// </summary>
        /// <param name="buttonName">Текст кнопки.</param>
        /// <param name="newRow">Если true — кнопка добавляется в новую строку.</param>
        /// <returns>Текущий экземпляр билдера.</returns>
        public ReplyKeyboardBuilder AddRequestContact(string buttonName, bool newRow = false)
        {
            this.AddButton(KeyboardButton.WithRequestContact(buttonName), newRow);
            return this;
        }

        /// <summary>
        /// Добавляет кнопку запроса геолокации.
        /// При нажатии Telegram отправит текущее местоположение пользователя.
        /// </summary>
        /// <param name="buttonName">Текст кнопки.</param>
        /// <param name="newRow">Если true — кнопка добавляется в новую строку.</param>
        /// <returns>Текущий экземпляр билдера.</returns>
        public ReplyKeyboardBuilder AddRequestLocation(string buttonName, bool newRow = false)
        {
            this.AddButton(KeyboardButton.WithRequestLocation(buttonName), newRow);
            return this;
        }

        /// <summary>
        /// Добавляет кнопку запроса выбора чата.
        /// Позволяет пользователю выбрать чат согласно параметрам запроса.
        /// </summary>
        /// <param name="buttonName">Текст кнопки.</param>
        /// <param name="requestChat">Объект параметров запроса чата.</param>
        /// <param name="newRow">Если true — кнопка добавляется в новую строку.</param>
        /// <returns>Текущий экземпляр билдера.</returns>
        public ReplyKeyboardBuilder AddRequestChat(string buttonName, KeyboardButtonRequestChat requestChat, bool newRow = false)
        {
            this.AddButton(KeyboardButton.WithRequestChat(buttonName, requestChat), newRow);
            return this;
        }

        /// <summary>
        /// Добавляет кнопку запроса выбора чата, указывая параметры запроса вручную.
        /// </summary>
        /// <param name="buttonName">Текст кнопки.</param>
        /// <param name="requestId">ID запроса.</param>
        /// <param name="chatIsChannel">True — выбирать только каналы; false — только группы/чаты.</param>
        /// <param name="newRow">Если true — кнопка добавляется в новую строку.</param>
        /// <returns>Текущий экземпляр билдера.</returns>
        public ReplyKeyboardBuilder AddRequestChat(string buttonName, int requestId, bool chatIsChannel, bool newRow = false)
        {
            this.AddButton(KeyboardButton.WithRequestChat(buttonName, requestId, chatIsChannel), newRow);
            return this;
        }

        /// <summary>
        /// Добавляет кнопку запроса выбора пользователей.
        /// </summary>
        /// <param name="buttonName">Текст кнопки.</param>
        /// <param name="requestUsers">Параметры запроса пользователей.</param>
        /// <param name="newRow">Если true — кнопка добавляется в новую строку.</param>
        /// <returns>Текущий экземпляр билдера.</returns>
        /// <returns></returns>
        public ReplyKeyboardBuilder AddRequestUsers(string buttonName, KeyboardButtonRequestUsers requestUsers, bool newRow = false)
        {
            this.AddButton(KeyboardButton.WithRequestUsers(buttonName, requestUsers), newRow);
            return this;
        }

        /// <summary>
        /// Добавляет кнопку запроса выбора пользователей,
        /// указывая параметры запроса вручную.
        /// </summary>
        /// <param name="buttonName">Текст кнопки.</param>
        /// <param name="requestId">ID запроса.</param>
        /// <param name="maxQuantity">Максимальное количество выбираемых пользователей.</param>
        /// <param name="newRow">Если true — кнопка добавляется в новую строку.</param>
        /// <returns>Текущий экземпляр билдера.</returns>
        public ReplyKeyboardBuilder AddRequestUsers(string buttonName, int requestId, int? maxQuantity = null, bool newRow = false)
        {
            this.AddButton(KeyboardButton.WithRequestUsers(buttonName, requestId, maxQuantity), newRow);
            return this;
        }

        /// <summary>
        /// Добавляет кнопку запроса создания опроса.
        /// При нажатии Telegram предложит пользователю создать опрос указанного типа.
        /// </summary>
        /// <param name="buttonName">Текст кнопки.</param>
        /// <param name="pollType">Тип опроса (обычный или квиз).</param>
        /// <param name="newRow">Если true — кнопка добавляется в новую строку.</param>
        /// <returns>Текущий экземпляр билдера.</returns>
        public ReplyKeyboardBuilder AddRequestPoll(string buttonName, KeyboardButtonPollType pollType, bool newRow = false)
        {
            this.AddButton(KeyboardButton.WithRequestPoll(buttonName, pollType), newRow);
            return this;
        }

        /// <summary>
        /// Добавляет указанное количество "пустых" кнопок — декоративных элементов,
        /// используемых для выравнивания или заполнения свободного места.
        /// </summary>
        /// <param name="count">Сколько пустых кнопок добавить.</param>
        /// <param name="newRow">Если true — каждая кнопка будет добавляться на новую строку.</param>
        /// <returns>Текущий экземпляр билдера.</returns>
        public ReplyKeyboardBuilder AddEmptyButton(int count = 1, bool newRow = false)
        {
            for (int i = 0; i < count; i++)
            {
                this.AddButton(new KeyboardButton(KEY_EMPTY_BUTTON_NAME), newRow);
                newRow = false;
            }

            return this;
        }

        #endregion

        #region Базовый класс

        /// <inheritdoc/>
        protected override void ReplaceEmptyButtons()
        {
            foreach (var row in buttons)
            {
                foreach (var button in row)
                {
                    if(button.Text.Equals(KEY_EMPTY_BUTTON_NAME, StringComparison.OrdinalIgnoreCase))
                        button.Text = emptyButtonName;
                }
            }
        }

        /// <inheritdoc/>
        public override ReplyKeyboardMarkup Build()
        {
            this.ReplaceEmptyButtons();

            var resultButtons = buttons.ToList();
            buttons.Clear();

            if (!string.IsNullOrEmpty(mainMenuButton) && mainMenuButtonPosition == MainMenuButtonPosition.Top)
            {
                this.AddButton(mainMenuButton);
                this.AddRow();
            }

            buttons.AddRange(resultButtons);

            if (!string.IsNullOrEmpty(mainMenuButton) && mainMenuButtonPosition == MainMenuButtonPosition.Bottom)
            {
                this.AddRow();
                this.AddButton(mainMenuButton);
            }

            buttons.RemoveAll(x => x == null || x.Count == 0);

            ReplyKeyboardMarkup replyKeyboardMarkup = new(buttons);
            replyKeyboardMarkup.IsPersistent = isPersistent;
            replyKeyboardMarkup.ResizeKeyboard = resizeKeyboard;
            replyKeyboardMarkup.OneTimeKeyboard = oneTimeKeyboard;
            replyKeyboardMarkup.InputFieldPlaceholder = inputFieldPlaceholder;
            replyKeyboardMarkup.Selective = selective;

            return replyKeyboardMarkup;
        }

        #endregion
    }

    /// <summary>
    /// Перечисление позиции кнопки главного меню.
    /// </summary>
    public enum MainMenuButtonPosition
    {
        /// <summary>
        /// Сверху.
        /// </summary>
        Top,
        /// <summary>
        /// Снизу.
        /// </summary>
        Bottom
    }
}

Last updated