using Microsoft.Extensions.Options; using PnvPanel.Infrastructure.Telegram; using Telegram.Bot; using Telegram.Bot.Polling; using Telegram.Bot.Types; using Telegram.Bot.Types.Enums; namespace PnvPanel.Api.Telegram; /// /// Бот — presentation-адаптер, хостится в процессе Api (long polling). Если BotToken не задан, /// не стартует — панель работает без бота. Апдейты обрабатывает PnvBotUpdateHandler, который /// вызывает те же CQRS-команды, что и веб, через собственный ISender. /// public sealed class TelegramBotHostedService( ITelegramBotClient botClient, PnvBotUpdateHandler updateHandler, IOptions options, ILogger logger ) : BackgroundService { private static readonly TimeSpan RetryDelay = TimeSpan.FromSeconds(30); protected override async Task ExecuteAsync(CancellationToken stoppingToken) { if (string.IsNullOrWhiteSpace(options.Value.BotToken)) { logger.LogWarning("Telegram__BotToken is not set — bot will not start."); return; } var receiverOptions = new ReceiverOptions { AllowedUpdates = [UpdateType.Message, UpdateType.CallbackQuery], DropPendingUpdates = true, }; logger.LogInformation("Telegram bot starting (long polling)..."); // ReceiveAsync может упасть с сетевой ошибкой (Telegram API недоступен/заблокирован) — // это НЕ должно ронять весь хост (BackgroundServiceExceptionBehavior.StopHost по умолчанию // убивает всё приложение при необработанном исключении в BackgroundService). Панель обязана // работать без бота, поэтому ловим, логируем и переподключаемся с паузой (см. CLAUDE.md). while (!stoppingToken.IsCancellationRequested) { try { await botClient.ReceiveAsync(updateHandler, receiverOptions, stoppingToken); } catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested) { // Штатная остановка вместе с приложением. } catch (Exception ex) { logger.LogError(ex, "Telegram bot polling failed, retrying in {Delay}", RetryDelay); try { await Task.Delay(RetryDelay, stoppingToken); } catch (OperationCanceledException) { // Остановка приложения во время паузы перед повтором. } } } } }