using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using ResourceMonitorService.Configuration; using ResourceMonitorService.Models; using Telegram.Bot; using Telegram.Bot.Exceptions; using Telegram.Bot.Types.Enums; namespace ResourceMonitorService.Services { public interface ITelegramNotificationService { Task SendAlertAsync(Alert alert); Task SendAlertResolvedAsync(Alert alert); Task IsEnabledAsync(); Task TestConnectionAsync(); } public class TelegramNotificationService : ITelegramNotificationService { private readonly ILogger _logger; private readonly TelegramSettings _telegramSettings; private readonly ITelegramBotClient? _botClient; public TelegramNotificationService( ILogger logger, IOptions settings) { _logger = logger; _telegramSettings = settings.Value.Telegram; if (_telegramSettings.IsEnabled && !string.IsNullOrEmpty(_telegramSettings.BotToken)) { try { _botClient = new TelegramBotClient(_telegramSettings.BotToken); } catch (Exception ex) { _logger.LogError(ex, "Failed to initialize Telegram bot client"); } } } public async Task IsEnabledAsync() { return await Task.FromResult(_telegramSettings.IsEnabled && _botClient != null); } public async Task TestConnectionAsync() { if (_botClient == null || !_telegramSettings.IsEnabled) return false; try { var me = await _botClient.GetMe(); _logger.LogInformation("Telegram bot connected successfully: @{Username}", me.Username); return true; } catch (Exception ex) { _logger.LogError(ex, "Failed to connect to Telegram bot"); return false; } } public async Task SendAlertAsync(Alert alert) { if (_botClient == null || !_telegramSettings.IsEnabled) return; // Check if we should send this type of alert if ((alert.Level == "Warning" && !_telegramSettings.SendWarningAlerts) || (alert.Level == "Critical" && !_telegramSettings.SendCriticalAlerts)) { return; } // Ignore alerts from svchost processes if (alert.Component.Contains("svchost", StringComparison.OrdinalIgnoreCase) || alert.Message.Contains("svchost", StringComparison.OrdinalIgnoreCase)) { _logger.LogInformation("Skipping Telegram alert for svchost process: {Component} - {Message}", alert.Component, alert.Message); return; } var message = FormatAlertMessage(alert, _telegramSettings.MessageTemplate); foreach (var chatId in _telegramSettings.ChatIds) { try { await _botClient.SendMessage( chatId: chatId, text: message, parseMode: ParseMode.Markdown, disableNotification: alert.Level == "Warning" // Don't ping for warnings ); _logger.LogInformation("Telegram alert sent to chat {ChatId}: {AlertLevel} - {Component}", chatId, alert.Level, alert.Component); } catch (ApiRequestException ex) { _logger.LogError(ex, "Failed to send Telegram alert to chat {ChatId}: {ErrorCode} - {Description}", chatId, ex.ErrorCode, ex.Message); } catch (Exception ex) { _logger.LogError(ex, "Unexpected error sending Telegram alert to chat {ChatId}", chatId); } } } public async Task SendAlertResolvedAsync(Alert alert) { if (_botClient == null || !_telegramSettings.IsEnabled || !_telegramSettings.SendResolutionNotifications) return; // Ignore alerts from svchost processes if (alert.Component.Contains("svchost", StringComparison.OrdinalIgnoreCase) || alert.Message.Contains("svchost", StringComparison.OrdinalIgnoreCase)) { _logger.LogInformation("Skipping Telegram resolution notification for svchost process: {Component} - {Message}", alert.Component, alert.Message); return; } var message = FormatAlertMessage(alert, _telegramSettings.ResolutionTemplate); foreach (var chatId in _telegramSettings.ChatIds) { try { await _botClient.SendMessage( chatId: chatId, text: message, parseMode: ParseMode.Markdown, disableNotification: true // Don't ping for resolutions ); _logger.LogInformation("Telegram resolution notification sent to chat {ChatId}: {Component}", chatId, alert.Component); } catch (ApiRequestException ex) { _logger.LogError(ex, "Failed to send Telegram resolution to chat {ChatId}: {ErrorCode} - {Description}", chatId, ex.ErrorCode, ex.Message); } catch (Exception ex) { _logger.LogError(ex, "Unexpected error sending Telegram resolution to chat {ChatId}", chatId); } } } private string FormatAlertMessage(Alert alert, string template) { var levelIcon = alert.Level switch { "Critical" => "🔴", "Warning" => "âš ī¸", _ => "â„šī¸" }; var componentIcon = alert.Component switch { "CPU" => "đŸ–Ĩī¸", "CPUTemp" => "đŸŒĄī¸", "Memory" => "💾", "GPU" => "🎮", "GPUTemp" => "đŸŒĄī¸", var disk when disk.StartsWith("Disk") => "đŸ’Ŋ", var process when process.StartsWith("ProcessMemory") => "âš™ī¸", _ => "📊" }; // Replace template placeholders var message = template .Replace("{Level}", alert.Level) .Replace("{Component}", alert.Component) .Replace("{Message}", EscapeMarkdown(alert.Message)) .Replace("{Timestamp}", alert.Timestamp.ToString("yyyy-MM-dd HH:mm:ss")) .Replace("{CurrentValue}", alert.CurrentValue.ToString("F1")) .Replace("{ThresholdValue}", alert.ThresholdValue.ToString("F1")); if (alert.ResolvedAt.HasValue) { message = message.Replace("{ResolvedAt}", alert.ResolvedAt.Value.ToString("yyyy-MM-dd HH:mm:ss")); } // Add icons message = $"{levelIcon} {componentIcon} {message}"; return message; } private static string EscapeMarkdown(string text) { // Escape special Markdown characters return text .Replace("_", "\\_") .Replace("*", "\\*") .Replace("[", "\\[") .Replace("]", "\\]") .Replace("(", "\\(") .Replace(")", "\\)") .Replace("~", "\\~") .Replace("`", "\\`") .Replace(">", "\\>") .Replace("#", "\\#") .Replace("+", "\\+") .Replace("-", "\\-") .Replace("=", "\\=") .Replace("|", "\\|") .Replace("{", "\\{") .Replace("}", "\\}") .Replace(".", "\\.") .Replace("!", "\\!"); } } }