diff --git a/src/CasaBot/CasaBotApp/Controllers/AutoScanController.cs b/src/CasaBot/CasaBotApp/Controllers/AutoScanController.cs new file mode 100644 index 0000000..4daebff --- /dev/null +++ b/src/CasaBot/CasaBotApp/Controllers/AutoScanController.cs @@ -0,0 +1,41 @@ +using AutoScan; +using CasaBotApp.TelegramBot; +using Microsoft.Extensions.Logging; + +namespace CasaBotApp.Controllers; + +public class AutoScanController : IController +{ + private readonly BotHandler _botHandler; + private readonly AutoScanApp _autoScanApp; + private readonly ILogger _logger; + + public AutoScanController(BotHandler botHandler, AutoScanApp autoScanApp, ILogger logger) + { + _botHandler = botHandler; + _autoScanApp = autoScanApp; + _logger = logger; + } + + public void Register() + { + _autoScanApp.OnScanCompleted = async () => + { + _logger.LogInformation("Scan completed at {At}", DateTime.Now); + try + { + var images = _autoScanApp.GetLastScanPictures(); + if (images.Length == 0) + { + await _botHandler.UpdateText("No images found"); + return; + } + await _botHandler.UpdateText($"Scan completed, found {images.Length} images"); + await _botHandler.UpdatePhotos(images); + }catch(Exception ex) + { + _logger.LogError(ex, "Error while sending message"); + } + }; + } +} \ No newline at end of file diff --git a/src/CasaBot/CasaBotApp/Controllers/BotController.cs b/src/CasaBot/CasaBotApp/Controllers/BotController.cs new file mode 100644 index 0000000..a2e135f --- /dev/null +++ b/src/CasaBot/CasaBotApp/Controllers/BotController.cs @@ -0,0 +1,143 @@ +using AutoScan; +using AutoScan.Interfaces; +using CasaBotApp.TelegramBot; +using ControlServer; +using Microsoft.Extensions.Logging; +using System.Diagnostics; +using Telegram.Bots.Types; +using BotCommand = CasaBotApp.TelegramBot.BotCommand; + +namespace CasaBotApp.Controllers; + +public class BotController : IController +{ + private readonly BotHandler _botHandler; + private readonly ILogger _logger; + private readonly AutoScanApp _autoScanApp; + private readonly ISnapshoter _snapshoter; + private readonly IControlServer _controlServer; + + + public BotController(BotHandler botHandler, ILogger logger, AutoScanApp autoScanApp, ISnapshoter snapshoter, IControlServer controlServer) + { + _botHandler = botHandler; + _logger = logger; + _autoScanApp = autoScanApp; + _snapshoter = snapshoter; + _controlServer = controlServer; + } + + public void Register() + { + _logger.LogInformation("Registering bot commands"); + var methods = GetType().GetMethods() + .Where(m => m.GetCustomAttributes(typeof(BotCommandAttribute), false).Length > 0) + .ToArray(); + foreach (var method in methods) + { + try + { + var attribute = (BotCommandAttribute)method.GetCustomAttributes(typeof(BotCommandAttribute), false)[0]; + var command = new BotCommand + { + Command = attribute.Command, + Description = attribute.Description, + Action = method.CreateDelegate>(this), + }; + _botHandler.RegisterCommand(command); + + _logger.LogInformation("Registered command: {Command} - {Description}", command.Command, command.Description); + } + catch (Exception e) + { + _logger.LogError(e, "Error registering command {Command}", method.Name); + } + + } + + HandleReply(); + } + + [BotCommand("/soyandre", "Soy Andre")] + public async Task HiAndre(TextMessage msg, BotCommand ctx) + { + _logger.LogInformation("Andre stoped by"); + await ctx.Responder(msg, "Hola vida, te amo mucho ❀️"); + } + + [BotCommand("/startScan", "Start a scan of last night images")] + public async Task StartScan(TextMessage msg, BotCommand ctx) + { + await ctx.Responder(msg, "Starting scan πŸ”πŸ“Ό"); + await _autoScanApp.StartNewScan(); + } + + [BotCommand("/lastscan", "Send the images from the last scan")] + public async Task LastScan(TextMessage msg, BotCommand ctx) + { + var images = _autoScanApp.GetLastScanPictures(); + if (images.Length == 0) + { + await ctx.Responder(msg, "No images found"); + return; + } + await _botHandler.SendPhotos(msg.Chat.Id, images); + + } + + [BotCommand("/now", "Get the current snapshot")] + public async Task CurrentSnapshot(TextMessage msg, BotCommand ctx) + { + var stopwatch = Stopwatch.StartNew(); + stopwatch.Start(); + var outputPath = await _snapshoter.TakeSnapshot(); + stopwatch.Stop(); + if (string.IsNullOrEmpty(outputPath)) + { + await ctx.Responder(msg, "Error taking snapshot, try later"); + return; + } + _ = _botHandler.SendPhoto(msg.Chat.Id, outputPath, $"Snapshot: {DateTime.Now:g} ({stopwatch.ElapsedMilliseconds} ms)"); + } + + [BotCommand("/disarm", "Disarm the Door Sensor")] + public async Task Disarm(TextMessage msg, BotCommand ctx) + { + await ctx.Responder(msg, "Disarming the door sensor"); + _controlServer.RequestDisarm(); + } + + private void HandleReply() + { + _botHandler.OnReply = async msg => + { + var originalMsg = msg.ReplyToMessage; + + // Check if the original message is a photo and has a caption + if (originalMsg is not PhotoMessage photoMessage || photoMessage.Caption is null) + return; + + var videoPath = _autoScanApp.GetVideoPath(photoMessage.Caption); + if (string.IsNullOrEmpty(videoPath)) + { + await _botHandler.SendText(msg.Chat.Id, "No video found for this image"); + return; + } + + await _botHandler.SendVideo(msg.Chat.Id, videoPath); + }; + } + + [AttributeUsage(AttributeTargets.Method)] + private class BotCommandAttribute : Attribute + { + public string Command { get; } + public string Description { get; } + + public BotCommandAttribute(string command, string description) + { + Command = command; + Description = description; + } + } +} \ No newline at end of file diff --git a/src/CasaBot/CasaBotApp/Controllers/CasaBotOrchestrator.cs b/src/CasaBot/CasaBotApp/Controllers/CasaBotOrchestrator.cs new file mode 100644 index 0000000..8d1a9fc --- /dev/null +++ b/src/CasaBot/CasaBotApp/Controllers/CasaBotOrchestrator.cs @@ -0,0 +1,19 @@ +namespace CasaBotApp.Controllers; + +public class CasaBotOrchestrator +{ + private readonly IEnumerable _controllers; + + public CasaBotOrchestrator(IEnumerable controllers) + { + _controllers = controllers; + } + + public void RegisterControllers() + { + foreach (var controller in _controllers) + { + controller.Register(); + } + } +} \ No newline at end of file diff --git a/src/CasaBot/CasaBotApp/Controllers/IController.cs b/src/CasaBot/CasaBotApp/Controllers/IController.cs new file mode 100644 index 0000000..66744ac --- /dev/null +++ b/src/CasaBot/CasaBotApp/Controllers/IController.cs @@ -0,0 +1,6 @@ +namespace CasaBotApp.Controllers; + +public interface IController +{ + void Register(); +} \ No newline at end of file diff --git a/src/CasaBot/CasaBotApp/Controllers/ServerController.cs b/src/CasaBot/CasaBotApp/Controllers/ServerController.cs new file mode 100644 index 0000000..e9843e5 --- /dev/null +++ b/src/CasaBot/CasaBotApp/Controllers/ServerController.cs @@ -0,0 +1,56 @@ +using AutoScan.Interfaces; +using CasaBotApp.TelegramBot; +using ControlServer; +using Microsoft.Extensions.Logging; + +namespace CasaBotApp.Controllers; + +public class ServerController : IController +{ + private readonly IControlServer _controlServer; + private readonly BotHandler _botHandler; + private readonly ISnapshoter _snapshoter; + private readonly ILogger _logger; + private readonly IShinobiLinkFactory _shinobiLinkFactory; + + public ServerController(IControlServer controlServer, ISnapshoter snapshoter, ILogger logger, IShinobiLinkFactory shinobiLinkFactory, BotHandler botHandler) + { + _controlServer = controlServer; + _snapshoter = snapshoter; + _logger = logger; + _shinobiLinkFactory = shinobiLinkFactory; + _botHandler = botHandler; + } + + public void Register() + { + _controlServer.OnEvent(async sensorEvent => + { + var mediaPath = await _snapshoter.TakeSnapshot(); + if (string.IsNullOrEmpty(mediaPath)) + { + await _botHandler.AlertText("Unauthorized access detected 🚨🚨🚨, but no media available"); + return; + } + if (sensorEvent.Type == EventType.Fired) + { + await _botHandler.AlertPhoto(mediaPath, + "Unauthorized access detected 🚨 🚨 🚨", + [ + new(OptionType.Url, "Camera Feed", _shinobiLinkFactory.BuildFeedLink()), + new(OptionType.Action, "Authorize", $"authorize-{sensorEvent.EventId}", (_, _ ) => + { + _logger.LogWarning("Authorizing event {EventId}", sensorEvent.EventId); + _controlServer.AuthorizeEvent(sensorEvent.EventId); + return Task.FromResult("Entrance authorized"); + }), + ]); + } + + if (sensorEvent.Type == EventType.DisarmedEntrance) + { + await _botHandler.UpdateText("Authorize access"); + } + }); + } +} \ No newline at end of file diff --git a/src/CasaBot/CasaBotApp/Extensions/AlarmBotOrchestration.cs b/src/CasaBot/CasaBotApp/Extensions/AlarmBotOrchestration.cs new file mode 100644 index 0000000..f9189c2 --- /dev/null +++ b/src/CasaBot/CasaBotApp/Extensions/AlarmBotOrchestration.cs @@ -0,0 +1,17 @@ + +using CasaBotApp.Controllers; +using Microsoft.Extensions.DependencyInjection; + +namespace CasaBotApp.Extensions; + +public static class AlarmBotOrchestration +{ + public static void AddCasaBotOrchestration(this IServiceCollection services) + { + services.AddSingleton(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + } + +} \ No newline at end of file diff --git a/src/CasaBot/CasaBotApp/Extensions/AlarmBotOrquestrator.cs b/src/CasaBot/CasaBotApp/Extensions/AlarmBotOrquestrator.cs deleted file mode 100644 index 59135bc..0000000 --- a/src/CasaBot/CasaBotApp/Extensions/AlarmBotOrquestrator.cs +++ /dev/null @@ -1,172 +0,0 @@ -using AutoScan; -using AutoScan.Interfaces; -using CasaBotApp.TelegramBot; -using ControlServer; -using Microsoft.Extensions.Logging; -using System.Diagnostics; -using Telegram.Bots.Types; -using BotCommand = CasaBotApp.TelegramBot.BotCommand; - -namespace CasaBotApp.Extensions; - -public class AlarmBotOrquestrator -{ - private readonly ILogger _logger; - private readonly BotHandler _botHandler; - private readonly AutoScanApp _autoScanApp; - private readonly IControlServer _controlServer; - private readonly IShinobiLinkFactory _shinobiLinkFactory; - private ISnapshoter _snapshoter; - - public AlarmBotOrquestrator(ILogger logger, BotHandler botHandler, AutoScanApp autoScanApp, - IControlServer controlServer, IShinobiLinkFactory shinobiLinkFactory, ISnapshoter snapshoter) - { - _logger = logger; - _botHandler = botHandler; - _autoScanApp = autoScanApp; - _controlServer = controlServer; - _shinobiLinkFactory = shinobiLinkFactory; - _snapshoter = snapshoter; - } - - public void RegisterCommands() - { - _botHandler.RegisterCommand(new BotCommand - { - Command = "/soyandre", - Description = "Soy Andre", - Action = async (message, ctx) => - { - await ctx.Responder(message, "Hola vida, te amo mucho ❀️"); - } - }); - _botHandler.RegisterCommand(new BotCommand - { - Command = "/startScan", - Description = "Start a scan of last night images", - Action = async (message, ctx) => - { - await ctx.Responder(message, "Starting scan πŸ”πŸ“Ό"); - await _autoScanApp.StartNewScan(); - } - }); - _botHandler.RegisterCommand(new BotCommand - { - Command = "/lastscan", - Description = "Send the images from the last scan", - Action = async (message, ctx) => - { - var images = _autoScanApp.GetLastScanPictures(); - if (images.Length == 0) - { - await ctx.Responder(message, "No images found"); - return; - } - await _botHandler.SendPhotos(message.Chat.Id, images); - } - }); - - _botHandler.RegisterCommand(new BotCommand() - { - Command = "/now", - Description = "Send the current snapshot", - Action = async (msg, ctx) => - { - var stopwatch = Stopwatch.StartNew(); - stopwatch.Start(); - var outputPath = await _snapshoter.TakeSnapshot(); - stopwatch.Stop(); - if (string.IsNullOrEmpty(outputPath)) - { - await ctx.Responder(msg, "Error taking snapshot"); - return; - } - _ = _botHandler.SendPhoto(msg.Chat.Id, outputPath, "Current snapshot"); - _ = _botHandler.SendText(msg.Chat.Id, $"It took {stopwatch.ElapsedMilliseconds} ms to take the picture"); - } - }); - _botHandler.RegisterCommand(new BotCommand() - { - Command = "/disarm", - Description = "Disarm the Door Sensor", - Action = async (msg, ctx) => - { - await ctx.Responder(msg, "Disarming the door sensor"); - _controlServer.RequestDisarm(); - } - }); - - _botHandler.OnReply = async msg => - { - var originalMsg = msg.ReplyToMessage; - - // Check if the original message is a photo and has a caption - if (originalMsg is not PhotoMessage photoMessage || photoMessage.Caption is null) - return; - - var videoPath = _autoScanApp.GetVideoPath(photoMessage.Caption); - if (string.IsNullOrEmpty(videoPath)) - { - await _botHandler.SendText(msg.Chat.Id, "No video found for this image"); - return; - } - - await _botHandler.SendVideo(msg.Chat.Id, videoPath); - }; - - } - - public void RegisterAutoScanApp() - { - _autoScanApp.OnScanCompleted = async () => - { - _logger.LogInformation("Scan completed at {At}", DateTime.Now); - try - { - var images = _autoScanApp.GetLastScanPictures(); - if (images.Length == 0) - { - await _botHandler.UpdateText("No images found"); - return; - } - await _botHandler.UpdateText($"Scan completed, found {images.Length} images"); - await _botHandler.UpdatePhotos(images); - }catch(Exception ex) - { - _logger.LogError(ex, "Error while sending message"); - } - }; - } - - public void RegisterControlServer() - { - _controlServer.OnEvent(async sensorEvent => - { - var mediaPath = await _snapshoter.TakeSnapshot(); - if (string.IsNullOrEmpty(mediaPath)) - { - await _botHandler.AlertText("Unauthorized access detected 🚨🚨🚨, but no media available"); - return; - } - if (sensorEvent.Type == EventType.Fired) - { - await _botHandler.AlertPhoto(mediaPath, - "Unauthorized access detected 🚨 🚨 🚨", - [ - new(OptionType.Url, "Camera Feed", _shinobiLinkFactory.BuildFeedLink()), - new(OptionType.Action, "Authorize", $"authorize-{sensorEvent.EventId}", (_, _ ) => - { - _logger.LogWarning("Authorizing event {EventId}", sensorEvent.EventId); - _controlServer.AuthorizeEvent(sensorEvent.EventId); - return Task.FromResult("Entrance authorized"); - }), - ]); - } - - if (sensorEvent.Type == EventType.DisarmedEntrance) - { - await _botHandler.UpdateText("Authorize access"); - } - }); - } -} \ No newline at end of file diff --git a/src/CasaBot/CasaBotApp/Program.cs b/src/CasaBot/CasaBotApp/Program.cs index c16717f..2fb9f04 100644 --- a/src/CasaBot/CasaBotApp/Program.cs +++ b/src/CasaBot/CasaBotApp/Program.cs @@ -1,5 +1,6 @@ ο»Ώusing AutoScan; using AutoScan.Options; +using CasaBotApp.Controllers; using CasaBotApp.Extensions; using CasaBotApp.TelegramBot; using ControlServer; @@ -40,7 +41,7 @@ hostBuilder.ConfigureServices((_, services) => services.AddPolling(); services.AddSingleton(sp => sp.GetService()!); - services.AddTransient(); + services.AddCasaBotOrchestration(); // To get notifications when a retry is performed @@ -65,16 +66,14 @@ var host = hostBuilder.Build(); var logger = host.Services.GetService>()!; var autoScanApp = host.Services.GetService()!; -var commandRegister = host.Services.GetRequiredService(); +var orchestrator = host.Services.GetRequiredService(); using var cts = new CancellationTokenSource(); _ = autoScanApp.Run(cts.Token); -commandRegister.RegisterAutoScanApp(); -commandRegister.RegisterCommands(); -commandRegister.RegisterControlServer(); +orchestrator.RegisterControllers(); logger.LogInformation("Bot started"); await host.RunAsync(cts.Token);