From 5f005b6496163267b34a9a3cba5f611a2a7ac31d Mon Sep 17 00:00:00 2001 From: Leonid Pershin Date: Thu, 2 Jul 2026 15:20:03 +0300 Subject: [PATCH] Enhance Telegram bot configuration and error handling - Updated `.env.example` to include a new `Telegram__ProxyUrl` setting for proxy configuration when accessing the Bot API, improving connectivity options. - Modified `Program.cs` to support proxy settings for the Telegram bot client, allowing for better handling of network restrictions. - Improved error handling in `TelegramBotHostedService` to ensure the bot can recover from network errors without crashing the application, implementing a retry mechanism with a delay. - Added `ProxyUrl` property to `TelegramOptions` for better configuration management. --- .env.example | 3 ++ backend/src/PnvPanel.Api/Program.cs | 23 ++++++++++--- .../Telegram/TelegramBotHostedService.cs | 33 +++++++++++++++---- .../Telegram/TelegramOptions.cs | 3 ++ 4 files changed, 52 insertions(+), 10 deletions(-) diff --git a/.env.example b/.env.example index 149b843..20e3ce9 100644 --- a/.env.example +++ b/.env.example @@ -43,6 +43,9 @@ Telegram__BotUsername=PnvPanelBot # и получают уведомления о запросах на активацию. Узнать id: @userinfobot. # Не связано с сид-админом выше — привязка Telegram к сид-админу делается вручную в UI. Telegram__AdminTelegramUserIds=123456789 +# Прокси для запросов к Bot API (обычно socks5://[user:pass@]host:port) — на случай, если Telegram +# заблокирован напрямую с сети сервера. Пусто (по умолчанию) — без прокси, прямое подключение. +# Telegram__ProxyUrl=socks5://127.0.0.1:39372 # ── ASP.NET Core ────────────────────────────────────────────────────────── ASPNETCORE_ENVIRONMENT=Production diff --git a/backend/src/PnvPanel.Api/Program.cs b/backend/src/PnvPanel.Api/Program.cs index 5ebc504..b65e001 100644 --- a/backend/src/PnvPanel.Api/Program.cs +++ b/backend/src/PnvPanel.Api/Program.cs @@ -59,11 +59,26 @@ builder.Services.AddSingleton(); // реальный HTTP-вызов через неё никогда не происходит (все вызывающие места сами проверяют BotToken). builder.Services.AddSingleton(sp => { - var options = sp.GetRequiredService>(); - var token = options.Value.BotToken; - return new TelegramBotClient(string.IsNullOrWhiteSpace(token) + var options = sp.GetRequiredService>().Value; + var token = string.IsNullOrWhiteSpace(options.BotToken) ? "0:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - : token); + : options.BotToken; + + if (string.IsNullOrWhiteSpace(options.ProxyUrl)) + return new TelegramBotClient(token); + + // Прокси (обычно socks5://) для запросов к Bot API — на случай, если Telegram недоступен напрямую + // с сети сервера. Пусто (по умолчанию) — без прокси, прямое подключение (см. .env.example). + var proxyUri = new Uri(options.ProxyUrl); + var proxy = new WebProxy(proxyUri); + if (!string.IsNullOrEmpty(proxyUri.UserInfo)) + { + var credentials = proxyUri.UserInfo.Split(':', 2); + proxy.Credentials = new NetworkCredential(credentials[0], credentials.Length > 1 ? credentials[1] : string.Empty); + } + + var handler = new SocketsHttpHandler { Proxy = proxy, UseProxy = true }; + return new TelegramBotClient(token, new HttpClient(handler)); }); // Scoped — зависит от IIdentityService (scoped), не Singleton. builder.Services.AddScoped(); diff --git a/backend/src/PnvPanel.Api/Telegram/TelegramBotHostedService.cs b/backend/src/PnvPanel.Api/Telegram/TelegramBotHostedService.cs index 2c52479..6189042 100644 --- a/backend/src/PnvPanel.Api/Telegram/TelegramBotHostedService.cs +++ b/backend/src/PnvPanel.Api/Telegram/TelegramBotHostedService.cs @@ -17,6 +17,8 @@ public sealed class TelegramBotHostedService( ILogger logger) : BackgroundService { + private static readonly TimeSpan RetryDelay = TimeSpan.FromSeconds(30); + protected override async Task ExecuteAsync(CancellationToken stoppingToken) { if (string.IsNullOrWhiteSpace(options.Value.BotToken)) @@ -33,13 +35,32 @@ public sealed class TelegramBotHostedService( logger.LogInformation("Telegram bot starting (long polling)..."); - try + // ReceiveAsync может упасть с сетевой ошибкой (Telegram API недоступен/заблокирован) — + // это НЕ должно ронять весь хост (BackgroundServiceExceptionBehavior.StopHost по умолчанию + // убивает всё приложение при необработанном исключении в BackgroundService). Панель обязана + // работать без бота, поэтому ловим, логируем и переподключаемся с паузой (см. CLAUDE.md). + while (!stoppingToken.IsCancellationRequested) { - await botClient.ReceiveAsync(updateHandler, receiverOptions, stoppingToken); - } - catch (OperationCanceledException) when (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) + { + // Остановка приложения во время паузы перед повтором. + } + } } } } diff --git a/backend/src/PnvPanel.Infrastructure/Telegram/TelegramOptions.cs b/backend/src/PnvPanel.Infrastructure/Telegram/TelegramOptions.cs index 3d9e490..3f8efd8 100644 --- a/backend/src/PnvPanel.Infrastructure/Telegram/TelegramOptions.cs +++ b/backend/src/PnvPanel.Infrastructure/Telegram/TelegramOptions.cs @@ -8,6 +8,9 @@ public sealed class TelegramOptions public string? BotUsername { get; init; } public string PublicSiteUrl { get; init; } = string.Empty; + /// Прокси для запросов к Bot API (обычно socks5://[user:pass@]host:port). Пусто — без прокси. + public string? ProxyUrl { get; init; } + /// Telegram id админов (через запятую) — авторизуют админ-кнопки в боте. public string? AdminTelegramUserIds { get; init; }