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.
This commit is contained in:
@@ -43,6 +43,9 @@ Telegram__BotUsername=PnvPanelBot
|
|||||||
# и получают уведомления о запросах на активацию. Узнать id: @userinfobot.
|
# и получают уведомления о запросах на активацию. Узнать id: @userinfobot.
|
||||||
# Не связано с сид-админом выше — привязка Telegram к сид-админу делается вручную в UI.
|
# Не связано с сид-админом выше — привязка Telegram к сид-админу делается вручную в UI.
|
||||||
Telegram__AdminTelegramUserIds=123456789
|
Telegram__AdminTelegramUserIds=123456789
|
||||||
|
# Прокси для запросов к Bot API (обычно socks5://[user:pass@]host:port) — на случай, если Telegram
|
||||||
|
# заблокирован напрямую с сети сервера. Пусто (по умолчанию) — без прокси, прямое подключение.
|
||||||
|
# Telegram__ProxyUrl=socks5://127.0.0.1:39372
|
||||||
|
|
||||||
# ── ASP.NET Core ──────────────────────────────────────────────────────────
|
# ── ASP.NET Core ──────────────────────────────────────────────────────────
|
||||||
ASPNETCORE_ENVIRONMENT=Production
|
ASPNETCORE_ENVIRONMENT=Production
|
||||||
|
|||||||
@@ -59,11 +59,26 @@ builder.Services.AddSingleton<IRealtimeNotifier, SignalRRealtimeNotifier>();
|
|||||||
// реальный HTTP-вызов через неё никогда не происходит (все вызывающие места сами проверяют BotToken).
|
// реальный HTTP-вызов через неё никогда не происходит (все вызывающие места сами проверяют BotToken).
|
||||||
builder.Services.AddSingleton<ITelegramBotClient>(sp =>
|
builder.Services.AddSingleton<ITelegramBotClient>(sp =>
|
||||||
{
|
{
|
||||||
var options = sp.GetRequiredService<IOptions<TelegramOptions>>();
|
var options = sp.GetRequiredService<IOptions<TelegramOptions>>().Value;
|
||||||
var token = options.Value.BotToken;
|
var token = string.IsNullOrWhiteSpace(options.BotToken)
|
||||||
return new TelegramBotClient(string.IsNullOrWhiteSpace(token)
|
|
||||||
? "0:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
? "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.
|
// Scoped — зависит от IIdentityService (scoped), не Singleton.
|
||||||
builder.Services.AddScoped<ITelegramNotifier, TelegramNotifier>();
|
builder.Services.AddScoped<ITelegramNotifier, TelegramNotifier>();
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ public sealed class TelegramBotHostedService(
|
|||||||
ILogger<TelegramBotHostedService> logger)
|
ILogger<TelegramBotHostedService> logger)
|
||||||
: BackgroundService
|
: BackgroundService
|
||||||
{
|
{
|
||||||
|
private static readonly TimeSpan RetryDelay = TimeSpan.FromSeconds(30);
|
||||||
|
|
||||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(options.Value.BotToken))
|
if (string.IsNullOrWhiteSpace(options.Value.BotToken))
|
||||||
@@ -33,13 +35,32 @@ public sealed class TelegramBotHostedService(
|
|||||||
|
|
||||||
logger.LogInformation("Telegram bot starting (long polling)...");
|
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);
|
try
|
||||||
}
|
{
|
||||||
catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested)
|
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)
|
||||||
|
{
|
||||||
|
// Остановка приложения во время паузы перед повтором.
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ public sealed class TelegramOptions
|
|||||||
public string? BotUsername { get; init; }
|
public string? BotUsername { get; init; }
|
||||||
public string PublicSiteUrl { get; init; } = string.Empty;
|
public string PublicSiteUrl { get; init; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>Прокси для запросов к Bot API (обычно socks5://[user:pass@]host:port). Пусто — без прокси.</summary>
|
||||||
|
public string? ProxyUrl { get; init; }
|
||||||
|
|
||||||
/// <summary>Telegram id админов (через запятую) — авторизуют админ-кнопки в боте.</summary>
|
/// <summary>Telegram id админов (через запятую) — авторизуют админ-кнопки в боте.</summary>
|
||||||
public string? AdminTelegramUserIds { get; init; }
|
public string? AdminTelegramUserIds { get; init; }
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user