Работа с пошаговыми командами

PRTelegramBot бот предоставляет функционал пошагового выполнения команд. Допустим нужно чтобы пользователь прошел по следующим этапам:

  • Ввод имени

  • Ввод даты рождения

Для создание следующего шага используется класс StepTelegram.

/// <summary>
/// Позволяет выполнить пользователю команды пошагово
/// </summary>
public class StepTelegram : IExecuteStep
{   
    /// <summary>
    /// Дочерние шаги
    /// </summary>
    public List<StepTelegram> Steps { get; private set; } = new List<StepTelegram>();

    /// <summary>
    /// Ссылка на метод который должен быть выполнен
    /// </summary>
    public Func<ITelegramBotClient, Update, Task> CommandDelegate { get; set; }
    
    /// <summary>
    /// Срок когда команда еще актуальна для выполнения
    /// </summary>
    public DateTime? ExpiredTime { get; set; }

    /// <summary>
    /// Кэш данных
    /// </summary>
    private ITelegramCache cache { get; set; }

    /// <summary>
    ///  Создает новый шаг
    /// </summary>
    /// <param name="command">Команда для выполнения</param>
    public StepTelegram(Func<ITelegramBotClient,Update, Task> command, ITelegramCache cache = null)
    {
        CommandDelegate = command;
        this.cache = cache;
    }

    /// <summary>
    ///  Создает новый шаг
    /// </summary>
    /// <param name="command">Команда для выполнения</param>
    /// <param name="steps">Коллекция шагов</param>
    public StepTelegram(Func<ITelegramBotClient, Update, Task> command, List<StepTelegram> steps, ITelegramCache cache = null)
    {
        CommandDelegate = command;
        Steps = steps;
        this.cache = cache;
    }

    /// <summary>
    /// Создает новый шаг
    /// </summary>
    /// <param name="command">Команда для выполнения</param>
    /// <param name="expiriedTime">Максимальный срок выполнения команды</param>
    public StepTelegram(Func<ITelegramBotClient, Update, Task> command, DateTime expiriedTime, ITelegramCache cache = null)
    {
        CommandDelegate = command;
        ExpiredTime = expiriedTime;
        this.cache = cache;
    }

    /// <summary>
    /// Создает новый шаг
    /// </summary>
    /// <param name="command">Команда для выполнения</param>
    /// <param name="expiriedTime">Максимальный срок выполнения команды</param>
     /// <param name="steps">Коллекция шагов</param>
    public StepTelegram(Func<ITelegramBotClient, Update, Task> command, DateTime expiriedTime, List<StepTelegram> steps)
    {
        CommandDelegate = command;
        ExpiredTime = expiriedTime;
        Steps = steps;
    }

    /// <summary>
    /// Выполнение шага
    /// </summary>
    /// <param name="botClient">telegram клиент</param>
    /// <param name="update">Update пользователя</param>
    /// <returns>Результат обработки</returns>
    public async Task<ResultExecuteStep> ExecuteStep(ITelegramBotClient botClient, Update update)
    {
        if (ExpiredTime != null && DateTime.Now > ExpiredTime)
        {
            update.ClearStepUserHandler();
            return ResultExecuteStep.ExpiredTime;
        }

        try
        {
            await CommandDelegate.Invoke(botClient, update);
            return ResultExecuteStep.Success;
        }
        catch(Exception ex) 
        {
            botClient.GetBotDataOrNull()!.InvokeErrorLog(ex);
            return ResultExecuteStep.Failure;
        }
    }

    /// <summary>
    /// Получение текущей команды
    /// </summary>
    /// <returns>Ссылка на метод обработки</returns>
    public Func<ITelegramBotClient, Update, Task> GetExecuteMethod()
    {
        return CommandDelegate;
    }

    /// <summary>
    /// Регистрация следующего шага
    /// </summary>
    /// <param name="nextStep">Метод для следующей обработки</param>
    /// <param name="expiriedTime">До какого времени команду можно выполнить</param>
    public void RegisterNextStep(Func<ITelegramBotClient, Update, Task> nextStep, DateTime? expiriedTime = null)
    {
        CommandDelegate = nextStep;
        ExpiredTime = expiriedTime;
    }

    /// <summary>
    /// Регистрация следующего шага
    /// </summary>
    /// <param name="nextStep">Метод для следующей обработки</param>
    /// <param name="addTime">До какого времени команду можно выполнить</param>
    public void RegisterNextStep(Func<ITelegramBotClient, Update, Task> nextStep, TimeSpan addTime)
    {
        CommandDelegate = nextStep;
        ExpiredTime = DateTime.Now.Add(addTime);
    }

    /// <summary>
    /// Получение текущего кэша
    /// </summary>
    /// <typeparam name="T">Класс для хранения кэша</typeparam>
    /// <returns>Кэш</returns>
    public T GetCache<T>() where T : ITelegramCache
    {
        return cache is T resultCache ? resultCache : default(T);
    }
}

Для работы с шагами используется следующие методы расширения StepExtension:

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

Для хранение кэша (промежуточных данных) создадим класс StepCache.

public class StepCache : ITelegramCache
{
    public string Name { get; set; }
    public string BirthDay { get; set; }
    public bool ClearData()
    {
        this.BirthDay = string.Empty; 
        this.Name = string.Empty;
        return true;
    }
}

Пример

/// <summary>
/// Пример работы пошагового выполнения команд
/// </summary>
public class ExampleStepCommand
{
    /// <summary>
    /// Напишите в чате "stepstart"
    /// Метод регистрирует следующий шаг пользователя
    /// </summary>
    [ReplyMenuHandler("stepstart")]
    public static async Task StepStart(ITelegramBotClient botClient, Update update)
    {
        string msg = "Тестирование функции пошагового выполнения\nНапишите ваше имя";
        //Регистрация обработчика для последовательной обработки шагов и сохранение данных
        update.RegisterStepHandler(new StepTelegram(StepOne, new StepCache()));
        await Helpers.Message.Send(botClient, update, msg);
    }

    /// <summary>
    /// При написание любого текста сообщения или нажатие на любую кнопку из reply для пользователя будет выполнен этот метод.
    /// Метод регистрирует следующий шаг с максимальным времени выполнения
    /// </summary>
    public static async Task StepOne(ITelegramBotClient botClient, Update update)
    {
        string msg = $"Шаг 1 - Ваше имя {update.Message.Text}" +
                    $"\nВведите дату рождения";
        //Получаем текущий обработчик
        var handler = update.GetStepHandler<StepTelegram>();
        //Записываем имя пользователя в кэш 
        handler!.GetCache<StepCache>().Name = update.Message.Text;
        //Регистрация следующего шага
        handler.RegisterNextStep(StepTwo);
        await Helpers.Message.Send(botClient, update, msg);
    }

    /// <summary>
    /// Напишите в чат любой текст и будет выполнена эта команда если у пользователя был записан следующий шаг
    /// </summary>
    public static async Task StepTwo(ITelegramBotClient botClient, Update update)
    {
        string msg = $"Шаг 2 - дата рождения {update.Message.Text}" +
                     $"\nНапиши любой текст, чтобы увидеть результат";
        //Получаем текущий обработчик
        var handler = update.GetStepHandler<StepTelegram>();
        //Записываем дату рождения
        handler!.GetCache<StepCache>().BirthDay = update.Message.Text;
        //Регистрация следующего шага с максимальным ожиданием выполнения этого шага 5 минут от момента регистрации
        handler.RegisterNextStep(StepThree, DateTime.Now.AddMinutes(5));
        //Настройки для сообщения
        var option = new OptionMessage();
        //Добавление пустого reply меню с кнопкой "Главное меню"
        //Функция является приоритетной, если пользователь нажмет эту кнопку будет выполнена функция главного меню, а не следующего шага.
        option.MenuReplyKeyboardMarkup = MenuGenerator.ReplyKeyboard(1, new List<string>(), true, new DictionaryJSON().GetButton(nameof(ReplyKeys.RP_MAIN_MENU)));
        await Helpers.Message.Send(botClient, update, msg, option);
    }


    /// <summary>
    /// Напишите в чат любой текст и будет выполнена эта команда если у пользователя был записан следующий шаг
    /// </summary>
    public static async Task StepThree(ITelegramBotClient botClient, Update update)
    {
        //Получение текущего обработчика
        var handler = update.GetStepHandler<StepTelegram>();
        //Получение текущего кэша
        var cache = handler!.GetCache<StepCache>(); ;
        string msg = $"Шаг 3 - Результат: Имя:{cache.Name} дата рождения:{cache.BirthDay}" +
                     $"\nПоследовательность шагов очищена.";
        //Последний шаг
        update.ClearStepUserHandler();
        await Helpers.Message.Send(botClient, update, msg);
    }

    /// <summary>
    /// Если есть следующий шаг, он будет проигнорирован при выполнение данной команды
    /// Потому что в ReplyMenuHandler значение первого аргумента установлено в true, что значит приоритетная команда
    /// </summary>
    [ReplyMenuHandler("ignorestep")]
    public static async Task IngoreStep(ITelegramBotClient botClient, Update update)
    {
        string msg = update.HasStepHandler() 
            ? "Следующий шаг проигнорирован" 
        : "Следующий шаг отсутствовал";
    
        await Helpers.Message.Send(botClient, update, msg);
    }
}

Завершение пошагового выполнение команд на последнем шаге

Начиная с версии 0.6 есть возможность взвести флаг, который оповестит, что это последний шаг в системе и пошаговое выполнение нужно завершить.

Пример:

var handler = update.GetStepHandler<StepTelegram>();
handler.LastStepExecuted = true;

Игнорирование базовых команд при выполнение пошаговых команд

Начиная с версии 0.6 есть возможность взвести флаг, который оповестит систему, что в данный момент нужно игнорировать все команды, кроме пошаговых.

Пример:

var handler = update.GetStepHandler<StepTelegram>();
handler.IgnoreBasicCommands = true;

Last updated