Compare commits
4 Commits
ea4c3bb64c
...
66aa064e2f
Author | SHA1 | Date | |
---|---|---|---|
66aa064e2f | |||
77b4c21571 | |||
064595c3e3 | |||
dabab6757b |
@ -66,9 +66,10 @@ public class Snapshoter : ISnapshoter
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
await process.WaitForExitAsync(timeoutSignal.Token);
|
await process.WaitForExitAsync(timeoutSignal.Token);
|
||||||
} catch (OperationCanceledException)
|
} catch (OperationCanceledException ex)
|
||||||
{
|
{
|
||||||
_logger.LogError("Taking snapshot timed out");
|
timer.Stop();
|
||||||
|
_logger.LogError("Taking snapshot timed out after {Elapsed} ms", timer.ElapsedMilliseconds);
|
||||||
process.Kill();
|
process.Kill();
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
41
src/CasaBot/CasaBotApp/Controllers/AutoScanController.cs
Normal file
41
src/CasaBot/CasaBotApp/Controllers/AutoScanController.cs
Normal 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");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
143
src/CasaBot/CasaBotApp/Controllers/BotController.cs
Normal file
143
src/CasaBot/CasaBotApp/Controllers/BotController.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
19
src/CasaBot/CasaBotApp/Controllers/CasaBotOrchestrator.cs
Normal file
19
src/CasaBot/CasaBotApp/Controllers/CasaBotOrchestrator.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
6
src/CasaBot/CasaBotApp/Controllers/IController.cs
Normal file
6
src/CasaBot/CasaBotApp/Controllers/IController.cs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
namespace CasaBotApp.Controllers;
|
||||||
|
|
||||||
|
public interface IController
|
||||||
|
{
|
||||||
|
void Register();
|
||||||
|
}
|
56
src/CasaBot/CasaBotApp/Controllers/ServerController.cs
Normal file
56
src/CasaBot/CasaBotApp/Controllers/ServerController.cs
Normal 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");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
17
src/CasaBot/CasaBotApp/Extensions/AlarmBotOrchestration.cs
Normal file
17
src/CasaBot/CasaBotApp/Extensions/AlarmBotOrchestration.cs
Normal 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>();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +1,6 @@
|
|||||||
using AutoScan;
|
using AutoScan;
|
||||||
using AutoScan.Options;
|
using AutoScan.Options;
|
||||||
|
using CasaBotApp.Controllers;
|
||||||
using CasaBotApp.Extensions;
|
using CasaBotApp.Extensions;
|
||||||
using CasaBotApp.TelegramBot;
|
using CasaBotApp.TelegramBot;
|
||||||
using ControlServer;
|
using ControlServer;
|
||||||
@ -40,7 +41,7 @@ hostBuilder.ConfigureServices((_, services) =>
|
|||||||
services.AddPolling<BotHandler>();
|
services.AddPolling<BotHandler>();
|
||||||
services.AddSingleton<IUpdateHandler>(sp => sp.GetService<BotHandler>()!);
|
services.AddSingleton<IUpdateHandler>(sp => sp.GetService<BotHandler>()!);
|
||||||
|
|
||||||
services.AddTransient<AlarmBotOrquestrator>();
|
services.AddCasaBotOrchestration();
|
||||||
|
|
||||||
// To get notifications when a retry is performed
|
// To get notifications when a retry is performed
|
||||||
|
|
||||||
@ -65,16 +66,14 @@ var host = hostBuilder.Build();
|
|||||||
var logger = host.Services.GetService<ILogger<Program>>()!;
|
var logger = host.Services.GetService<ILogger<Program>>()!;
|
||||||
var autoScanApp = host.Services.GetService<AutoScanApp>()!;
|
var autoScanApp = host.Services.GetService<AutoScanApp>()!;
|
||||||
|
|
||||||
var commandRegister = host.Services.GetRequiredService<AlarmBotOrquestrator>();
|
var orchestrator = host.Services.GetRequiredService<CasaBotOrchestrator>();
|
||||||
|
|
||||||
|
|
||||||
using var cts = new CancellationTokenSource();
|
using var cts = new CancellationTokenSource();
|
||||||
|
|
||||||
_ = autoScanApp.Run(cts.Token);
|
_ = autoScanApp.Run(cts.Token);
|
||||||
|
|
||||||
commandRegister.RegisterAutoScanApp();
|
orchestrator.RegisterControllers();
|
||||||
commandRegister.RegisterCommands();
|
|
||||||
commandRegister.RegisterControlServer();
|
|
||||||
|
|
||||||
logger.LogInformation("Bot started");
|
logger.LogInformation("Bot started");
|
||||||
await host.RunAsync(cts.Token);
|
await host.RunAsync(cts.Token);
|
||||||
|
@ -16,22 +16,21 @@ namespace CasaBotApp.TelegramBot;
|
|||||||
public class BotHandler : IUpdateHandler
|
public class BotHandler : IUpdateHandler
|
||||||
{
|
{
|
||||||
private readonly ILogger<BotHandler> _logger;
|
private readonly ILogger<BotHandler> _logger;
|
||||||
private readonly TelegramOptions _telegramOptions;
|
|
||||||
private readonly List<Chat> _subscribers = [];
|
private readonly List<Chat> _subscribers = [];
|
||||||
private readonly List<Chat> _subscribersAlarm = [];
|
private readonly List<Chat> _subscribersAlarm = [];
|
||||||
private readonly Dictionary<string, BotCommand> _commands;
|
private readonly Dictionary<string, BotCommand> _commands;
|
||||||
//TODO hacerlo mejor.
|
|
||||||
private readonly Dictionary<string, CallbackQueueItem> _callbackFunctions = new();
|
private readonly Dictionary<string, CallbackQueueItem> _callbackFunctions = new();
|
||||||
private record CallbackQueueItem(DateTime inserted, Func<string, long, Task<string>> callback);
|
private record CallbackQueueItem(DateTime inserted, Func<string, long, Task<string>> callback);
|
||||||
|
|
||||||
public Func<TextMessage, Task>? OnReply { get; set; } = null;
|
public Func<TextMessage, Task>? OnReply { get; set; }
|
||||||
|
|
||||||
private readonly IBotClient _bot;
|
private readonly IBotClient _bot;
|
||||||
|
|
||||||
public BotHandler(IBotClient bot, IOptions<TelegramOptions> telegramConfiguration, ILogger<BotHandler> logger)
|
public BotHandler(IBotClient bot, IOptions<TelegramOptions> telegramConfiguration, ILogger<BotHandler> logger)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_telegramOptions = telegramConfiguration.Value;
|
var telegramOptions = telegramConfiguration.Value;
|
||||||
_bot = bot;
|
_bot = bot;
|
||||||
_commands = [];
|
_commands = [];
|
||||||
RegisterCommand(new()
|
RegisterCommand(new()
|
||||||
@ -41,7 +40,6 @@ public class BotHandler : IUpdateHandler
|
|||||||
Action = RegisterUser,
|
Action = RegisterUser,
|
||||||
Responder = Respond
|
Responder = Respond
|
||||||
});
|
});
|
||||||
{
|
|
||||||
RegisterCommand(new()
|
RegisterCommand(new()
|
||||||
{
|
{
|
||||||
Command = "/registeralarm",
|
Command = "/registeralarm",
|
||||||
@ -49,20 +47,12 @@ public class BotHandler : IUpdateHandler
|
|||||||
Action = RegisterUserAlarm,
|
Action = RegisterUserAlarm,
|
||||||
Responder = Respond
|
Responder = Respond
|
||||||
});
|
});
|
||||||
}
|
|
||||||
RegisterCommand(new()
|
|
||||||
{
|
|
||||||
Command = "/photo",
|
|
||||||
Description = "Get a photo",
|
|
||||||
Action = SendImageTest,
|
|
||||||
Responder = Respond
|
|
||||||
});
|
|
||||||
|
|
||||||
foreach (var subs in _telegramOptions.SubscribedChatIds)
|
foreach (var subs in telegramOptions.SubscribedChatIds)
|
||||||
{
|
{
|
||||||
Subscribe(subs);
|
Subscribe(subs);
|
||||||
}
|
}
|
||||||
foreach(var sub in _telegramOptions.SubscribedAlarmsChatIds)
|
foreach(var sub in telegramOptions.SubscribedAlarmsChatIds)
|
||||||
{
|
{
|
||||||
SubscribeAlarm(sub);
|
SubscribeAlarm(sub);
|
||||||
}
|
}
|
||||||
@ -105,12 +95,13 @@ public class BotHandler : IUpdateHandler
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Send text message to all subscribers
|
/// Send a text message to all subscribers
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="message"></param>
|
/// <param name="message"></param>
|
||||||
public Task UpdateText(string message) => UpdateTextInt(_subscribers, message);
|
public Task UpdateText(string message) => UpdateTextInt(_subscribers, message);
|
||||||
public Task AlertText(string message) => UpdateTextInt(_subscribersAlarm, message);
|
public Task AlertText(string message) => UpdateTextInt(_subscribersAlarm, message);
|
||||||
public async Task UpdateTextInt(List<Chat> subscribers,string message)
|
|
||||||
|
private async Task UpdateTextInt(List<Chat> subscribers,string message)
|
||||||
{
|
{
|
||||||
if (subscribers.Count == 0)
|
if (subscribers.Count == 0)
|
||||||
{
|
{
|
||||||
@ -137,7 +128,7 @@ public class BotHandler : IUpdateHandler
|
|||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Send photo to all subscribers
|
/// Send a photo to all subscribers
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="path"></param>
|
/// <param name="path"></param>
|
||||||
/// <param name="caption">Optional message with photo</param>
|
/// <param name="caption">Optional message with photo</param>
|
||||||
@ -145,6 +136,13 @@ public class BotHandler : IUpdateHandler
|
|||||||
public Task UpdatePhoto(string path, string? caption = null, IEnumerable<MsgOption>? options = null) =>
|
public Task UpdatePhoto(string path, string? caption = null, IEnumerable<MsgOption>? options = null) =>
|
||||||
UpdatePhotoInt(_subscribers, path, caption, options);
|
UpdatePhotoInt(_subscribers, path, caption, options);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Send a photo to all alert subscribers
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path"></param>
|
||||||
|
/// <param name="caption"></param>
|
||||||
|
/// <param name="options"></param>
|
||||||
|
/// <returns></returns>
|
||||||
public Task AlertPhoto(string path, string? caption = null, IEnumerable<MsgOption>? options = null) =>
|
public Task AlertPhoto(string path, string? caption = null, IEnumerable<MsgOption>? options = null) =>
|
||||||
UpdatePhotoInt(_subscribersAlarm, path, caption, options);
|
UpdatePhotoInt(_subscribersAlarm, path, caption, options);
|
||||||
|
|
||||||
@ -194,7 +192,7 @@ public class BotHandler : IUpdateHandler
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task HandleQueueExpiration()
|
private Task HandleQueueExpiration()
|
||||||
{
|
{
|
||||||
//remove expired items with more than 3 hs
|
//remove expired items with more than 3 hs
|
||||||
var expired = _callbackFunctions.Where(x => x.Value.inserted.AddHours(3) < DateTime.Now).ToArray();
|
var expired = _callbackFunctions.Where(x => x.Value.inserted.AddHours(3) < DateTime.Now).ToArray();
|
||||||
@ -203,6 +201,7 @@ public class BotHandler : IUpdateHandler
|
|||||||
_callbackFunctions.Remove(item.Key);
|
_callbackFunctions.Remove(item.Key);
|
||||||
_logger.LogDebug("Removed expired callback function {Key}", item.Key);
|
_logger.LogDebug("Removed expired callback function {Key}", item.Key);
|
||||||
}
|
}
|
||||||
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -251,8 +250,8 @@ public class BotHandler : IUpdateHandler
|
|||||||
|
|
||||||
if (_subscribers.Count < 1) return;
|
if (_subscribers.Count < 1) return;
|
||||||
|
|
||||||
var caches = response.Result.Select(x => (x as PhotoMessage).PhotoSet.LastOrDefault()?.Id).ToList();
|
var caches = response.Result.Select(x => (x as PhotoMessage)!.PhotoSet.LastOrDefault()?.Id).ToList();
|
||||||
var media = caches.Select(x => new CachedPhoto(x)).ToList();
|
var media = caches.Select(x => new CachedPhoto(x!)).ToList();
|
||||||
|
|
||||||
//send to the rest of the subscribers
|
//send to the rest of the subscribers
|
||||||
foreach (var subscriber in _subscribers.Skip(1).ToList())
|
foreach (var subscriber in _subscribers.Skip(1).ToList())
|
||||||
@ -272,14 +271,6 @@ public class BotHandler : IUpdateHandler
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task SendImageTest(TextMessage msg, BotCommand _)
|
|
||||||
{
|
|
||||||
await using var stream = File.OpenRead(@"C:\Users\GuillermoMarcel\Pictures\prueba.jpeg");
|
|
||||||
|
|
||||||
var send = new SendPhotoFile(msg.Chat.Id.ToString(), stream);
|
|
||||||
await _bot.HandleAsync(send);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task SendPhoto(long chatId, string content, string caption)
|
public async Task SendPhoto(long chatId, string content, string caption)
|
||||||
{
|
{
|
||||||
await using var stream = File.OpenRead(content);
|
await using var stream = File.OpenRead(content);
|
||||||
@ -370,7 +361,15 @@ public class BotHandler : IUpdateHandler
|
|||||||
|
|
||||||
private async Task OnMessage(TextMessage msg)
|
private async Task OnMessage(TextMessage msg)
|
||||||
{
|
{
|
||||||
if(!_commands.TryGetValue(msg.Text, out var command))
|
var cmd = msg.Text;
|
||||||
|
//Deep link support
|
||||||
|
// link format: https://t.me/your_bot?start=command
|
||||||
|
// what we see: "/start command"
|
||||||
|
if (cmd.StartsWith("/start "))
|
||||||
|
{
|
||||||
|
cmd = $"/{cmd[7..]}";
|
||||||
|
}
|
||||||
|
if(!_commands.TryGetValue(cmd, out var command))
|
||||||
{
|
{
|
||||||
if (msg.ReplyToMessage != null && OnReply is not null)
|
if (msg.ReplyToMessage != null && OnReply is not null)
|
||||||
{
|
{
|
||||||
@ -381,12 +380,12 @@ public class BotHandler : IUpdateHandler
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (command?.Action is null)
|
if (command.Action is null)
|
||||||
{
|
{
|
||||||
_logger.LogError("Command {Command} has no action", msg.Text);
|
_logger.LogError("Command {Command} has no action", msg.Text);
|
||||||
}
|
}
|
||||||
|
|
||||||
await command!.Action!(msg, command);
|
await command.Action!(msg, command);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task SendHelp(TextMessage msg)
|
private async Task SendHelp(TextMessage msg)
|
||||||
@ -411,7 +410,7 @@ public class BotHandler : IUpdateHandler
|
|||||||
|
|
||||||
_subscribers.Add(msg.Chat);
|
_subscribers.Add(msg.Chat);
|
||||||
_logger.LogInformation("User {User} ({id}) registered to receive messages", msg.Chat.FirstName, msg.Chat.Id);
|
_logger.LogInformation("User {User} ({id}) registered to receive messages", msg.Chat.FirstName, msg.Chat.Id);
|
||||||
await Respond(msg, "You are registered to receive messages every minute");
|
await Respond(msg, "You are registered to receive non-alert messages (Nightly scans)");
|
||||||
}
|
}
|
||||||
private async Task RegisterUserAlarm(TextMessage msg, BotCommand _)
|
private async Task RegisterUserAlarm(TextMessage msg, BotCommand _)
|
||||||
{
|
{
|
||||||
@ -434,6 +433,13 @@ public class BotHandler : IUpdateHandler
|
|||||||
_bot.HandleAsync(new SendPhotoFile(chat.Id.ToString(), path));
|
_bot.HandleAsync(new SendPhotoFile(chat.Id.ToString(), path));
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Entry point for the bot
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="bot">The bot client used to receive and send messages</param>
|
||||||
|
/// <param name="update">Type of update and message info</param>
|
||||||
|
/// <param name="cst"></param>
|
||||||
|
/// <returns></returns>
|
||||||
public Task HandleAsync(IBotClient bot, Update update, CancellationToken cst)
|
public Task HandleAsync(IBotClient bot, Update update, CancellationToken cst)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
@ -76,8 +76,10 @@ public class ControlServer : IControlServer
|
|||||||
}
|
}
|
||||||
response.Close();
|
response.Close();
|
||||||
|
|
||||||
if (notify)
|
if (notify && _onEventRecived is not null)
|
||||||
{
|
{
|
||||||
|
//don't await this, we don't care about the result.
|
||||||
|
//and we don't want to block the server thread.
|
||||||
_ = _onEventRecived?.Invoke(sensorEvent);
|
_ = _onEventRecived?.Invoke(sensorEvent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -106,8 +108,8 @@ public class ControlServer : IControlServer
|
|||||||
writer.Write(message);
|
writer.Write(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
public record UpdateResponse(bool disarm, Card[] cards);
|
private record UpdateResponse(bool disarm, Card[] cards);
|
||||||
public record Card(string id, string name);
|
private record Card(string id, string name);
|
||||||
private bool HandleSensorEvent(SensorEvent se)
|
private bool HandleSensorEvent(SensorEvent se)
|
||||||
{
|
{
|
||||||
if (se.Type == EventType.Update && _disarmRequestPending)
|
if (se.Type == EventType.Update && _disarmRequestPending)
|
||||||
@ -118,9 +120,10 @@ public class ControlServer : IControlServer
|
|||||||
}
|
}
|
||||||
|
|
||||||
_events.TryGetValue(se.EventId, out var storedEvent);
|
_events.TryGetValue(se.EventId, out var storedEvent);
|
||||||
|
//New One
|
||||||
if (storedEvent is null)
|
if (storedEvent is null)
|
||||||
{
|
{
|
||||||
//New One
|
//ESP does not send this type of event yet
|
||||||
if (se.Type == EventType.DisarmedEntrance)
|
if (se.Type == EventType.DisarmedEntrance)
|
||||||
{
|
{
|
||||||
_disarmRequestPending = false;
|
_disarmRequestPending = false;
|
||||||
@ -129,11 +132,18 @@ public class ControlServer : IControlServer
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Alarm is armed and fired.
|
||||||
if (se.Type == EventType.Fired)
|
if (se.Type == EventType.Fired)
|
||||||
{
|
{
|
||||||
//Check pending desarmed.
|
//Check pending disarm request.
|
||||||
se.Authorization = _disarmRequestPending ? Authorization.Authorized : Authorization.Unauthorized;
|
if (_disarmRequestPending)
|
||||||
|
{
|
||||||
|
se.Authorization = Authorization.Authorized;
|
||||||
|
se.Type = EventType.DisarmedEntrance;
|
||||||
_disarmRequestPending = false;
|
_disarmRequestPending = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
se.Authorization = Authorization.Unauthorized;
|
||||||
_events.Add(se.EventId, se);
|
_events.Add(se.EventId, se);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ namespace ControlServer;
|
|||||||
public record SensorEvent(long EventId, EventType Type, string Data)
|
public record SensorEvent(long EventId, EventType Type, string Data)
|
||||||
{
|
{
|
||||||
public Authorization Authorization { get; set; }
|
public Authorization Authorization { get; set; }
|
||||||
|
public EventType Type { get; set; } = Type;
|
||||||
};
|
};
|
||||||
|
|
||||||
public enum EventType
|
public enum EventType
|
||||||
|
Loading…
Reference in New Issue
Block a user