refactor: extract controllers

This commit is contained in:
Guillermo Marcel 2025-05-07 12:50:31 -03:00
parent 064595c3e3
commit 77b4c21571
8 changed files with 286 additions and 177 deletions

View File

@ -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<AutoScanController> _logger;
public AutoScanController(BotHandler botHandler, AutoScanApp autoScanApp, ILogger<AutoScanController> 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");
}
};
}
}

View File

@ -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<BotController> _logger;
private readonly AutoScanApp _autoScanApp;
private readonly ISnapshoter _snapshoter;
private readonly IControlServer _controlServer;
public BotController(BotHandler botHandler, ILogger<BotController> 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<Func<TextMessage, BotCommand, Task>>(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;
}
}
}

View File

@ -0,0 +1,19 @@
namespace CasaBotApp.Controllers;
public class CasaBotOrchestrator
{
private readonly IEnumerable<IController> _controllers;
public CasaBotOrchestrator(IEnumerable<IController> controllers)
{
_controllers = controllers;
}
public void RegisterControllers()
{
foreach (var controller in _controllers)
{
controller.Register();
}
}
}

View File

@ -0,0 +1,6 @@
namespace CasaBotApp.Controllers;
public interface IController
{
void Register();
}

View File

@ -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<ServerController> _logger;
private readonly IShinobiLinkFactory _shinobiLinkFactory;
public ServerController(IControlServer controlServer, ISnapshoter snapshoter, ILogger<ServerController> 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");
}
});
}
}

View File

@ -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<CasaBotOrchestrator>();
services.AddTransient<IController, AutoScanController>();
services.AddTransient<IController, BotController>();
services.AddTransient<IController, ServerController>();
}
}

View File

@ -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<AlarmBotOrquestrator> _logger;
private readonly BotHandler _botHandler;
private readonly AutoScanApp _autoScanApp;
private readonly IControlServer _controlServer;
private readonly IShinobiLinkFactory _shinobiLinkFactory;
private ISnapshoter _snapshoter;
public AlarmBotOrquestrator(ILogger<AlarmBotOrquestrator> 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");
}
});
}
}

View File

@ -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<BotHandler>();
services.AddSingleton<IUpdateHandler>(sp => sp.GetService<BotHandler>()!);
services.AddTransient<AlarmBotOrquestrator>();
services.AddCasaBotOrchestration();
// To get notifications when a retry is performed
@ -65,16 +66,14 @@ var host = hostBuilder.Build();
var logger = host.Services.GetService<ILogger<Program>>()!;
var autoScanApp = host.Services.GetService<AutoScanApp>()!;
var commandRegister = host.Services.GetRequiredService<AlarmBotOrquestrator>();
var orchestrator = host.Services.GetRequiredService<CasaBotOrchestrator>();
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);