Работа с пошаговыми командами
PRTelegramBot бот предоставляет функционал пошагового выполнения команд. Допустим нужно чтобы пользователь прошел по следующим этапам:
Ввод имени
Ввод даты рождения
Для создание следующего шага используется класс StepTelegram.
/// <summary>
/// Позволяет выполнить пользователю команды пошагово.
/// </summary>
public sealed class StepTelegram : IExecuteStep
{
#region Свойства и константы
/// <summary>
/// Ссылка на метод который должен быть выполнен.
/// </summary>
public Func<IBotContext, Task> CommandDelegate { get; set; }
/// <summary>
/// До какого времени команду можно выполнить.
/// </summary>
public DateTime? ExpiredTime { get; set; }
/// <summary>
/// Кэш данных.
/// </summary>
private ITelegramCache cache { get; set; }
#endregion
#region IExecuteStep
/// <inheritdoc/>
public bool LastStepExecuted { get; set; }
/// <inheritdoc/>
public bool IgnoreBasicCommands { get; set; }
/// <inheritdoc/>
public async Task<ExecuteStepResult> ExecuteStep(IBotContext context)
{
if (ExpiredTime is not null && DateTime.Now > ExpiredTime)
{
context.Update.ClearStepUserHandler();
return ExecuteStepResult.ExpiredTime;
}
try
{
await CommandDelegate.Invoke(context);
return ExecuteStepResult.Success;
}
catch (Exception ex)
{
context.Current.Events.OnErrorLogInvoke(new ErrorLogEventArgs(context, ex));
return ExecuteStepResult.Failure;
}
}
/// <inheritdoc/>
public Func<IBotContext, Task> GetExecuteMethod()
{
return CommandDelegate;
}
/// <inheritdoc/>
public bool CanExecute()
{
return ExpiredTime is null || DateTime.Now < ExpiredTime;
}
#endregion
#region Методы
/// <summary>
/// Регистрация следующего шага.
/// </summary>
/// <param name="nextStep">Метод для следующей обработки.</param>
public void RegisterNextStep(Func<IBotContext, Task> nextStep)
{
RegisterNextStep(nextStep, null);
}
/// <summary>
/// Регистрация следующего шага.
/// </summary>
/// <param name="nextStep">Метод для следующей обработки.</param>
/// <param name="addTime">До какого времени команду можно выполнить</param>
public void RegisterNextStep(Func<IBotContext, Task> nextStep, TimeSpan addTime)
{
RegisterNextStep(nextStep, DateTime.Now.Add(addTime));
}
/// <summary>
/// Регистрация следующего шага.
/// </summary>
/// <param name="nextStep">Метод для следующей обработки.</param>
/// <param name="expiriedTime"> До какого времени команду можно выполнить.</param>
public void RegisterNextStep(Func<IBotContext, Task> nextStep, DateTime? expiriedTime)
{
RegisterNextStep(nextStep, expiriedTime, false);
}
/// <summary>
/// Регистрация следующего шага.
/// </summary>
/// <param name="nextStep">Метод для следующей обработки.</param>
/// <param name="expiriedTime"> До какого времени команду можно выполнить.</param>
/// <param name="ignoreBasicCommands">Игнорировать базовые команды при выполнение шагов.</param>
public void RegisterNextStep(Func<IBotContext, Task> nextStep, DateTime? expiriedTime, bool ignoreBasicCommands)
{
CommandDelegate = nextStep;
ExpiredTime = expiriedTime;
IgnoreBasicCommands = ignoreBasicCommands;
}
/// <summary>
/// Получение текущего кэша
/// </summary>
/// <typeparam name="T">Класс для хранения кэша</typeparam>
/// <returns>Кэш</returns>
public T GetCache<T>()
{
return cache is T resultCache ? resultCache : default;
}
#endregion
#region Конструкторы класса
/// <summary>
/// Создать новый следующий шаг.
/// </summary>
/// <param name="command">Команда для выполнения</param>
public StepTelegram(Func<IBotContext, Task> command)
: this(command, null, null) { }
/// <summary>
/// Создать новый следующий шаг.
/// </summary>
/// <param name="command">Команда для выполнения.</param>
/// <param name="cache">Кэш.</param>
public StepTelegram(Func<IBotContext, Task> command, ITelegramCache cache)
: this(command, null, cache, false) { }
/// <summary>
/// Создать новый следующий шаг.
/// </summary>
/// <param name="command">Команда для выполнения.</param>
/// <param name="expiriedTime">Максимальный срок выполнения команды после чего команда будет проигнорирована.</param>
public StepTelegram(Func<IBotContext, Task> command, DateTime expiriedTime)
: this(command, expiriedTime, null, false) { }
/// <summary>
/// Создать новый следующий шаг.
/// </summary>
/// <param name="command">Команда для выполнения.</param>
/// <param name="expiriedTime">Максимальный срок выполнения команды после чего команда будет проигнорирована.</param>
/// <param name="cache">Кэш.</param>
public StepTelegram(Func<IBotContext, Task> command, DateTime? expiriedTime, ITelegramCache cache)
: this(command, expiriedTime, cache, false) { }
/// <summary>
/// Создать новый следующий шаг.
/// </summary>
/// <param name="command">Команда для выполнения.</param>
/// <param name="expiriedTime">Максимальный срок выполнения команды после чего команда будет проигнорирована.</param>
/// <param name="cache">Кэш.</param>
/// <param name="ignoreBasicCommands">Игнорировать базовые команды при выполнение шагов.</param>
public StepTelegram(Func<IBotContext, Task> command, DateTime? expiriedTime, ITelegramCache cache, bool ignoreBasicCommands)
{
this.cache = cache;
IgnoreBasicCommands = ignoreBasicCommands;
CommandDelegate = command;
ExpiredTime = expiriedTime;
}
#endregion
}Для работы с шагами используется следующие методы расширения 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(IBotContext context)
{
string msg = "Тестирование функции пошагового выполнения\nНапишите ваше имя";
//Регистрация обработчика для последовательной обработки шагов и сохранение данных
context.RegisterStepHandler(new StepTelegram(StepOne, new StepCache()));
await Helpers.Message.Send(context, msg);
}
/// <summary>
/// При написание любого текста сообщения или нажатие на любую кнопку из reply для пользователя будет выполнен этот метод.
/// Метод регистрирует следующий шаг с максимальным времени выполнения
/// </summary>
public static async Task StepOne(IBotContext context)
{
string msg = $"Шаг 1 - Ваше имя {update.Message.Text}" +
$"\nВведите дату рождения";
//Получаем текущий обработчик
var handler = context.GetStepHandler<StepTelegram>();
//Записываем имя пользователя в кэш
handler!.GetCache<StepCache>().Name = update.Message.Text;
//Регистрация следующего шага
handler.RegisterNextStep(StepTwo);
await Helpers.Message.Send(context, msg);
}
/// <summary>
/// Напишите в чат любой текст и будет выполнена эта команда если у пользователя был записан следующий шаг
/// </summary>
public static async Task StepTwo(IBotContext context)
{
string msg = $"Шаг 2 - дата рождения {context.Update.Message.Text}" +
$"\nНапиши любой текст, чтобы увидеть результат";
//Получаем текущий обработчик
var handler = context.GetStepHandler<StepTelegram>();
//Записываем дату рождения
handler!.GetCache<StepCache>().BirthDay = context.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(context, msg, option);
}
/// <summary>
/// Напишите в чат любой текст и будет выполнена эта команда если у пользователя был записан следующий шаг
/// </summary>
public static async Task StepThree(IBotContext context)
{
//Получение текущего обработчика
var handler = context.GetStepHandler<StepTelegram>();
//Получение текущего кэша
var cache = handler!.GetCache<StepCache>();
string msg = $"Шаг 3 - Результат: Имя:{cache.Name} дата рождения:{cache.BirthDay}" +
$"\nПоследовательность шагов очищена.";
//Последний шаг
context.ClearStepUserHandler();
await Helpers.Message.Send(context, msg);
}
/// <summary>
/// Если есть следующий шаг, он будет проигнорирован при выполнение данной команды
/// Потому что в ReplyMenuHandler значение первого аргумента установлено в true, что значит приоритетная команда
/// </summary>
[ReplyMenuHandler("ignorestep")]
public static async Task IngoreStep(IBotContext context)
{
string msg = context.HasStepHandler()
? "Следующий шаг проигнорирован"
: "Следующий шаг отсутствовал";
await Helpers.Message.Send(context, msg);
}
}Завершение пошагового выполнение команд на последнем шаге
Начиная с версии 0.6 есть возможность взвести флаг, который оповестит, что это последний шаг в системе и пошаговое выполнение нужно завершить.
Пример:
var handler = context.GetStepHandler<StepTelegram>();
handler.LastStepExecuted = true;Игнорирование базовых команд при выполнение пошаговых команд
Начиная с версии 0.6 есть возможность взвести флаг, который оповестит систему, что в данный момент нужно игнорировать все команды, кроме пошаговых.
Пример:
var handler = context.GetStepHandler<StepTelegram>();
handler.IgnoreBasicCommands = true;Last updated