From ea02a726ef1d6fce4b048450cf214b269b954564 Mon Sep 17 00:00:00 2001 From: Guillermo Marcel Date: Tue, 18 Feb 2025 13:06:49 -0300 Subject: [PATCH] refactor: change dependency to use dotnet runtime instead of aspnet --- src/CasaBot/CasaBotApp/BotHandler.cs | 183 +++++++++++------------ src/CasaBot/CasaBotApp/CasaBotApp.csproj | 13 +- src/CasaBot/CasaBotApp/Dockerfile | 44 +----- src/CasaBot/CasaBotApp/Program.cs | 97 +++++------- 4 files changed, 143 insertions(+), 194 deletions(-) diff --git a/src/CasaBot/CasaBotApp/BotHandler.cs b/src/CasaBot/CasaBotApp/BotHandler.cs index 9deb410..59f54ca 100644 --- a/src/CasaBot/CasaBotApp/BotHandler.cs +++ b/src/CasaBot/CasaBotApp/BotHandler.cs @@ -1,90 +1,82 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using Telegram.Bot; -using Telegram.Bot.Polling; -using Telegram.Bot.Types; -using Telegram.Bot.Types.Enums; -using Telegram.Bot.Types.ReplyMarkups; +using Telegram.Bots; +using Telegram.Bots.Extensions.Polling; +using Telegram.Bots.Requests.Usernames; +using Telegram.Bots.Types; +using File = System.IO.File; namespace CasaBotApp; -public class BotHandler +public class BotHandler : IUpdateHandler { private readonly ILogger _logger; private readonly TelegramOptions _telegramOptions; - private readonly List _subscribers = []; + private readonly List _subscribers = []; - private TelegramBotClient? _bot; + private readonly IBotClient _bot; - - public BotHandler(IOptions telegramConfiguration, ILogger logger) + public BotHandler(IBotClient bot, IOptions telegramConfiguration, ILogger logger) { _logger = logger; - if (string.IsNullOrEmpty(telegramConfiguration.Value.BotToken)) - { - _logger.LogError("Bot token is not provided"); - throw new ArgumentException("Bot token is required", nameof(telegramConfiguration)); - } _telegramOptions = telegramConfiguration.Value; + _bot = bot; } public void Start(CancellationToken cancellationToken) { - _logger.LogInformation("Starting bot..."); - if (string.IsNullOrEmpty(_telegramOptions.BotToken)) + foreach (var subs in _telegramOptions.SubscribedChatIds) { - _logger.LogError("Bot token is not provided"); + Subscribe(subs); + } + } + + public void Subscribe(long id) + { + if (_subscribers.Any(x => x.Id == id)) + { + _logger.LogWarning("User {Id} is already subscribed", id); return; } - _bot = new TelegramBotClient(_telegramOptions.BotToken, cancellationToken: cancellationToken); - - _bot.OnError += OnError; - _bot.OnMessage += OnMessage; - _bot.OnUpdate += OnUpdate; + _subscribers.Add(new Chat() + { + Id = id + }); + _logger.LogInformation("User {Id} subscribed to receive messages", id); } - public void Update(string message) + public async Task Update(string message) { - if (_bot is null) - { - _logger.LogWarning("Bot is not initialized yet"); - return; - } - if (_subscribers.Count == 0) { _logger.LogWarning("No subscribers to send message to"); return; } + var replacement = new List<(Chat from, Chat to)>(); foreach (var subscriber in _subscribers) { - _bot.SendMessage(subscriber, message); + var response = await SndTxt(subscriber, message); + if (subscriber.FirstName is null) + { + replacement.Add((subscriber, response.Result.Chat)); + } + } + + foreach (var rep in replacement) + { + _subscribers.Remove(rep.from); + _subscribers.Add(rep.to); } } - public void Subscribe(long id) - { - if (_subscribers.Any(s => s.Identifier == id)) - { - _logger.LogWarning("User {Id} is already subscribed", id); - return; - } - _subscribers.Add(new ChatId(id)); - _logger.LogInformation("User {Id} subscribed to receive messages", id); - } public async Task UpdatePhoto(string path) { - if (_bot is null) - { - _logger.LogWarning("Bot is not initialized yet"); - return; - } - + if (_subscribers.Count == 0) { _logger.LogWarning("No subscribers to send message to"); @@ -94,84 +86,85 @@ public class BotHandler foreach (var subscriber in _subscribers) { await using var stream = File.OpenRead(path); - var inputFile = InputFile.FromStream(stream, Path.GetFileName(path)); - await _bot.SendPhoto(subscriber, inputFile, "Detected object"); + await SndPhoto(subscriber, stream); } } private async Task SendImageTest(long id) { - if (_bot is null) - { - _logger.LogWarning("Bot is not initialized yet"); - return; - } - await using var stream = File.OpenRead(@"C:\Users\GuillermoMarcel\Pictures\prueba.jpeg"); - var inputFile = InputFile.FromStream(stream, "gorda.jpeg"); - await _bot.SendPhoto(new ChatId(id), inputFile, "mi gorda"); - } - - // method to handle errors in polling or in your OnMessage/OnUpdate code - private Task OnError(Exception exception, HandleErrorSource source) - { - _logger.LogError(exception, "Error in {Source}", source); - return Task.CompletedTask; + var send = new SendPhotoFile(id.ToString(), stream); + await _bot.HandleAsync(send); } - private async Task OnMessage(Message msg, UpdateType type) - { - if (_bot is null) - { - _logger.LogWarning("Bot is not initialized yet"); - return; - } + private async Task OnMessage(TextMessage msg) + { + switch (msg.Text) { - case "/start": - await _bot.SendMessage(msg.Chat, "Welcome! Pick one direction", - replyMarkup: new InlineKeyboardMarkup().AddButtons("Left", "Right")); - return; - case "/register": - if (_subscribers.Any(s => s.Identifier == msg.Chat.Id)) + if (_subscribers.Any(c => c.Id == msg.Chat.Id)) { - await _bot.SendMessage(msg.Chat, "You are already registered to receive messages"); + await Respond(msg, "You are already registered to receive messages"); return; } _subscribers.Add(msg.Chat); - _logger.LogInformation("User {User} registered to receive messages", msg.Chat); - await _bot.SendMessage(msg.Chat, "You are registered to receive messages every minute"); + _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"); + return; case "/photo": await SendImageTest(msg.Chat.Id); return; + case "/soyandre": + await Respond(msg, "Hola vida, te amo mucho ❤️"); + return; + default: - _logger.LogInformation("Received {Type} '{Text}' in {Chat}", type, msg.Text, msg.Chat); - await _bot.SendMessage(msg.Chat, "Commands: \n/start to start over \n/register to get messages every minute \n/photo to get a photo"); - // await _bot.SendMessage(msg.Chat, "Hola vida te amo mucho ❤️"); + _logger.LogInformation("Received '{Text}' in {Chat}", msg.Text, msg.Chat); + + const string commands = + "Commands: \n/help to show the commands \n/register to get messages every minute \n/photo to get a photo \n/soyandre por si sos andre"; + await Respond(msg, commands); break; } } - // method that handle other types of updates received by the bot: - private async Task OnUpdate(Update update) - { - if (_bot is null) - { - _logger.LogWarning("Bot is not initialized yet"); - return; - } + + private Task> SndTxt(long id, string txt) => _bot.HandleAsync(new SendText(id.ToString(), txt)); + private Task> SndTxt(Chat chat, string txt) => SndTxt(chat.Id, txt); + private Task> Respond(Message msg, string txt) => SndTxt(msg.Chat.Id, txt); + private Task> SndPhoto(Chat chat, FileStream path) => + _bot.HandleAsync(new SendPhotoFile(chat.Id.ToString(), path)); - if (update is { CallbackQuery: { } query }) // non-null CallbackQuery + + public Task HandleAsync(IBotClient bot, Update update, CancellationToken cst) + { + try { - await _bot.AnswerCallbackQuery(query.Id, $"You picked {query.Data}"); - await _bot.SendMessage(query.Message!.Chat, $"User {query.From} clicked on {query.Data}"); - } + return update switch + { + MessageUpdate u when u.Data is TextMessage message => + OnMessage(message), + + EditedMessageUpdate u when u.Data is TextMessage message => + bot.HandleAsync(new SendText(message.Chat.Id.ToString(), message.Text) + { + ReplyToMessageId = message.Id + }, cst), + + _ => Task.CompletedTask + }; + }catch (Exception e) + { + _logger.LogError(e, "Error handling update"); + return Task.CompletedTask; + } } + } \ No newline at end of file diff --git a/src/CasaBot/CasaBotApp/CasaBotApp.csproj b/src/CasaBot/CasaBotApp/CasaBotApp.csproj index 62133d6..a3bbc4f 100644 --- a/src/CasaBot/CasaBotApp/CasaBotApp.csproj +++ b/src/CasaBot/CasaBotApp/CasaBotApp.csproj @@ -16,7 +16,18 @@ - + + + + + + + + + + + + diff --git a/src/CasaBot/CasaBotApp/Dockerfile b/src/CasaBot/CasaBotApp/Dockerfile index 68a6570..ce2d979 100644 --- a/src/CasaBot/CasaBotApp/Dockerfile +++ b/src/CasaBot/CasaBotApp/Dockerfile @@ -1,43 +1,6 @@ -#FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/runtime:9.0 AS base -#ARG TARGETARCH -#USER root -#WORKDIR /app -# -#FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build -#ARG BUILD_CONFIGURATION=Release -#WORKDIR /src -#COPY ["CasaBotApp/CasaBotApp.csproj", "CasaBotApp/"] -#COPY ["AutoScan/AutoScan.csproj", "AutoScan/"] -#RUN dotnet restore "CasaBotApp/CasaBotApp.csproj" -a $TARGETARCH -#COPY . . -#WORKDIR "/src/CasaBotApp" -#RUN dotnet build "CasaBotApp.csproj" -c $BUILD_CONFIGURATION -o /app/build -# -#FROM build AS publish -#ARG BUILD_CONFIGURATION=Release -#RUN dotnet publish "CasaBotApp.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false -# -#FROM base AS final -# -## Install pyhon and run "pipx install dvr-scan[opencv]" to install dependency -##RUN apt-get update && apt-get install -y python3 python3-pip python3-venv -##RUN python3 -m pip install dvr-scan[opencv] --break-system-packages -##RUN pip3 install pipx --break-system-packages -##RUN pipx install dvr-scan[opencv-headless] -# -#WORKDIR /app -#COPY --from=publish /app/publish . -##ENTRYPOINT ["dotnet", "CasaBotApp.dll"] -# -## interactive shell -#CMD ["/bin/bash"] - - - - -# Learn about building .NET container images: +# Learn about building .NET container images: # https://github.com/dotnet/dotnet-docker/blob/main/samples/README.md -FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:9.0-noble AS build +FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:9.0 AS build ARG TARGETARCH WORKDIR /source @@ -53,8 +16,7 @@ RUN dotnet publish "CasaBotApp/CasaBotApp.csproj" -a $TARGETARCH --no-restore -o # Runtime stage -#FROM mcr.microsoft.com/dotnet/runtime:9.0-noble -FROM mcr.microsoft.com/dotnet/aspnet:9.0-noble +FROM mcr.microsoft.com/dotnet/runtime:9.0 WORKDIR /app COPY --link --from=build /app . USER $APP_UID diff --git a/src/CasaBot/CasaBotApp/Program.cs b/src/CasaBot/CasaBotApp/Program.cs index 934b395..1e61b3e 100644 --- a/src/CasaBot/CasaBotApp/Program.cs +++ b/src/CasaBot/CasaBotApp/Program.cs @@ -3,47 +3,57 @@ using AutoScan.Options; using CasaBotApp; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; +using Telegram.Bots; +using Telegram.Bots.Extensions.Polling; -// See https://aka.ms/new-console-template for more information - -var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"); +var environment = Environment.GetEnvironmentVariable("CASABOT_ENVIRONMENT"); IConfigurationRoot configuration = new ConfigurationBuilder() .AddJsonFile($"appsettings.json", true, true) .AddJsonFile($"appsettings.{environment}.json", true, true) .AddEnvironmentVariables() .Build(); -var services = new ServiceCollection(); - -services.AddSingleton(configuration); -services.AddLogging(builder => +var hostBuilder = new HostBuilder(); +hostBuilder.ConfigureServices((_, services) => { - builder.AddConfiguration(configuration.GetSection("Logging")); - //add time to logs - builder.AddSimpleConsole(options => + services.AddSingleton(configuration); + services.AddLogging(builder => { - options.IncludeScopes = true; - options.SingleLine = true; - options.TimestampFormat = "[HH:mm:ss] "; + builder.AddConfiguration(configuration.GetSection("Logging")); + //add time to logs + builder.AddSimpleConsole(options => + { + options.IncludeScopes = true; + options.SingleLine = true; + options.TimestampFormat = "[HH:mm:ss] "; + }); }); + + services.Configure(configuration.GetSection("Telegram")); + services.Configure(configuration.GetSection("AutoScan")); + services.Configure(configuration.GetSection("Shinobi")); + + services.AddSingleton(); + + var token = configuration["Telegram:BotToken"] ?? ""; + services.AddBotClient(token); + services.AddPolling(); + services.AddSingleton(sp => sp.GetService()!); + + + services.AddAutoScan(); + services.AddHttpClient(); }); -services.Configure(configuration.GetSection("Telegram")); -services.Configure(configuration.GetSection("AutoScan")); -services.Configure(configuration.GetSection("Shinobi")); -services.AddSingleton(); +var host = hostBuilder.Build(); -services.AddAutoScan(); -services.AddHttpClient(); -var serviceProvider = services.BuildServiceProvider(); - -var logger = serviceProvider.GetService>()!; - -var botHandler = serviceProvider.GetService()!; -var autoScanApp = serviceProvider.GetService()!; +var logger = host.Services.GetService>()!; +var botHandler = host.Services.GetService()!; +var autoScanApp = host.Services.GetService()!; using var cts = new CancellationTokenSource(); @@ -52,40 +62,12 @@ botHandler.Start(cts.Token); _ = SendMessageToSubscribers(cts.Token); -// var videoFileName = configuration.GetValue("VideoFileName") ?? "video.mp4"; -// if (configuration.GetValue("Fetch")) -// { -// var shinobiConnector = serviceProvider.GetService()!; -// await shinobiConnector.FetchLastVideo(videoFileName); -// } -// -// if (configuration.GetValue("Scan")) -// { -// var dvrScanner = serviceProvider.GetService()!; -// await dvrScanner.ScanVideos(cts.Token); -// } -// -// if(configuration.GetValue("Screenshot")) -// { -// var detected = "2025-02-12T07-00-02.DSME_0001.avi"; -// var ffmpegWrapper = serviceProvider.GetService()!; -// var duration = await ffmpegWrapper.GetVideoDuration($@".\media\detected\{detected}"); -// logger.LogInformation("Video duration: {Duration}", duration); -// var middleTime = (duration / 2).Add(TimeSpan.FromSeconds(0.5)); -// -// //Extract frame at middle time -// var screenshotPath = $@".\media\detected\{detected}-ss.png"; -// await ffmpegWrapper.ExtractFrame($@".\media\detected\{detected}", screenshotPath, middleTime); -// logger.LogInformation("Screenshot extracted at {MiddleTime}", middleTime); -// -// //botHandler.Subscribe(115151151); -// await botHandler.UpdatePhoto(screenshotPath); -// } +_ = host.RunAsync(cts.Token); logger.LogInformation("Bot started"); logger.LogInformation("Press any key to stop the bot..."); Console.ReadLine(); -cts.Cancel(); // stop the bot +await cts.CancelAsync(); // stop the bot return; @@ -94,8 +76,9 @@ async Task SendMessageToSubscribers(CancellationToken cancellationToken) { while (!cancellationToken.IsCancellationRequested) { - await Task.Delay(TimeSpan.FromMinutes(1), cancellationToken); - botHandler.Update("Hello from CasaBot! at " + DateTime.Now); + await Task.Delay(TimeSpan.FromSeconds(30), cancellationToken); + logger.LogInformation("Sending message to subscribers"); + await botHandler.Update($"Hello from CasaBot! at {DateTime.Now}"); } }