Бот - кулинарная книга для Telegram Найти в Telegram - @CookingHelperBot
Ишин Данил, Гладышева Татьяна, Немцев Евгений
Данный бот позволяет по некоторым критериям получить рецепт. В данный момент поддерживаются следующие команды:
- /recipe - найти рецепт по названию.
- /ingr - получить список рецептов, в которых содержится указанный ингридиент.
- /all - отобразить список всех рецептов.
- /help - информация о поддерживаемых командах.
Легко добавлять новые типы команд и использовать различаные базы данных. Также существует более глобальная точка расширения IBot, о которой будет расказано ниже.
Расширяемость команд продемонстрирована выше. Для баз данных - ArrayDatabase (используется массив, который изначально засериализован). Для демонстрации IBot как точки расширения помимо CookBot, был написан HelloBot (который здоровается с вами в ответ на любое сообщение).
-
Команды.
Для того, чтобы создать новую команду достаточно реализовать интерфейс IBotCommand. У команды должно быть имя(свойство Name), описание(свойство Description), а также метод Execute, в который передается ссылка на базу данных и аргументы команды. Примеры реализации можно посмотреть в этой папке .
-
База данных.
Для того, чтобы добавить новую базу данных, нужно реализовать дженерик интерфейс IDatabase. Этот интерфейс декларирует два метода GetAllSuitable, GetAnySuitable, в которые передается лямбда-выражение, которому должен удовлетворять искомый объект.
-
Интерфейс IBot
Для реализации этого интерфейса необходимо реализовать метод HandleCommand, который возвращает ответ на полученное сообщение. Благодаря этому интерфейсу при работе с TelgramAPI(или другим API), мы можем не привязываться к конкретному боту. В нашем проекте есть две реализации этого интерфейса CookBot и HelloBot. Так же есть класс TelegramHandler, который работает с TelegramAPI и ему в конструктор передается реализация интерфейса IBot. Тем самым, создав нового бота, мы можем просто передать его в конструктор TelegramHandler и не думать снова о том, как работать с TelegramAPI.
Имеем три слоя:
- Инфраструктура Содержит всю работу с базами данных. Не взаимодействует с двумя остальными слоями. Пример использования базы данных в слое приложения при выполнении команды:
try
{
var result = Database
.GetAnySuitable(x => string.Equals(x.Name, recipeName, StringComparison.CurrentCultureIgnoreCase))
.GetPrintableView();
return new BotCommandResult(BotCode.Good, result);
}
catch (InvalidOperationException)
{
return new BotCommandResult(BotCode.Bad, "К сожалению, ничего подходящего не найдено :(");
}
-
Предметный слой В нем описана модель рецепта. Главный класс - класс рецепта Он использует несколько вспомогательных классов, например, класс, отвечающий за количество и единицу измерения ингредиента и сам ингредиент. Все они расположены тут
-
Слой приложения Содержит непосредственно работу с Telegram API и команды бота. Не используется двумя предыдущими слоями, но использует их. Демонстрация работы с БД была выше. Работа с предметным слоем происходит при обработке команд (Получили рецепт и попросили у него отдаваемый пользователю вид):
public BotCommandResult Execute(string[] arguments)
{
var suitableRecipes = Database.GetAllSuitable(x => arguments
.All(z => x.Components.Keys.Select(y => y.Name.ToLower()).Contains(z.ToLower())));
if (!suitableRecipes.Any())
return new BotCommandResult(BotCode.Bad, "К сожалению, ничего подходящего не найдено: (");
return new BotCommandResult(BotCode.Good,
string.Join("\n", suitableRecipes.Select(res => res.GetPrintableView())));
}
Один из наиболее важных классов слоя приложения - класс TelegramHandler.cs Содержит всю работу с Телеграм АПИ и использует интерфейс IBot
Для создания DI-контейнера была выбрала библиотека Ninject. Итоговая сборка осуществляется в классе Program.cs Наиболее неочевидные моменты:
- Циклическая зависимость. Команда хелп должна знать обо всех зарегистрированных командах.
container.Bind<Lazy<List<IBotCommand>>>().ToConstant(new Lazy<List<IBotCommand>>(() => container
.GetAll<IBotCommand>()
.ToList()))
Для этого нам приходится воспользоваться классом Lazy<T>
в классе команды Help:
private readonly Lazy<List<IBotCommand>> commands;
- Cигнлтоны
container.Bind<IBotCommand>().To<RecipeByNameCommand>().InSingletonScope();
container.Bind<IBotCommand>().To<RecipeByIngredientsCommand>().InSingletonScope();
container.Bind<IBotCommand>().To<RecipeListCommand>().InSingletonScope();
Данные класс сделаны сигнлтонами для того, чтобы не создавалось несколько их экземпляров (при создании команды Help ей необходимы и другие зарегистрированные команды, экземпляры которых DI-контейнер создаст еще раз)