using FrameworkDAL.DTO.EIPConversation; using GB5Shared.DTO.Framework.Login; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using System; using System.Linq; using System.Net.Http; using System.Text; using System.Text.Json; using System.Threading; using System.Threading.Tasks; namespace FrameworkBLL.EIPConversation.EIPHandlers.ChannelHandler { public class TelegramChannelHandler : IEIPChannelHandler { // Interface property public string ChannelName => "TELEGRAM"; private readonly ILogger _logger; private readonly IConfiguration _configuration; private readonly HttpClient _httpClient; public TelegramChannelHandler( ILogger logger, IConfiguration configuration, HttpClient httpClient) { _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); _httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient)); } // Single CanHandle implementation public bool CanHandle(string channel) => string.Equals(channel, ChannelName, StringComparison.OrdinalIgnoreCase); public async Task SendAsync( EIPResponseContext context, LoginDTO loginDTO, CancellationToken cancellationToken) { if (context == null) throw new ArgumentNullException(nameof(context)); if (loginDTO == null) throw new ArgumentNullException(nameof(loginDTO)); var botToken = _configuration["MessageHubSettings:Telegram:BotToken"]; var baseUrl = _configuration["MessageHubSettings:Telegram:BaseUrl"]; var defaultRecipient = _configuration["MessageHubSettings:Telegram:DefaultRecipient"]; if (string.IsNullOrWhiteSpace(botToken)) throw new InvalidOperationException("Telegram BotToken not configured."); if (string.IsNullOrWhiteSpace(baseUrl)) throw new InvalidOperationException("Telegram BaseUrl not configured."); var recipient = string.IsNullOrWhiteSpace(context.Recipient) ? defaultRecipient : context.Recipient; if (string.IsNullOrWhiteSpace(recipient)) throw new ArgumentException("Telegram recipient is required."); var stopwatch = System.Diagnostics.Stopwatch.StartNew(); try { cancellationToken.ThrowIfCancellationRequested(); _logger.LogInformation( "TELEGRAM SEND START | Tenant={Tenant} | ChatId={ChatId}", context.TenantId, recipient); // QR code: send as photo using the qrserver image service if (!string.IsNullOrWhiteSpace(context.QrCodeData)) { var encoded = Uri.EscapeDataString(context.QrCodeData); var photoUrl = $"https://api.qrserver.com/v1/create-qr-code/?size=200x200&data={encoded}"; var photoApi = $"{baseUrl}/bot{botToken}/sendPhoto"; var photoPayload = JsonSerializer.Serialize(new { chat_id = recipient, photo = photoUrl, caption = context.Message ?? string.Empty }); var photoResponse = await _httpClient.PostAsync( photoApi, new StringContent(photoPayload, Encoding.UTF8, "application/json"), cancellationToken); stopwatch.Stop(); _logger.LogInformation( "TELEGRAM QR PHOTO | Tenant={Tenant} | DurationMs={Duration}", context.TenantId, stopwatch.ElapsedMilliseconds); return new EIPChannelResponseDTO { IsSent = photoResponse.IsSuccessStatusCode, Status = photoResponse.IsSuccessStatusCode ? "SENT" : "FAILED" }; } string apiUrl; string json; // Location request: send ReplyKeyboardMarkup with request_location button if (context.RequestLocation) { apiUrl = $"{baseUrl}/bot{botToken}/sendMessage"; json = JsonSerializer.Serialize(new { chat_id = recipient, text = context.Message ?? "Please share your current location:", parse_mode = "HTML", reply_markup = new { keyboard = new[] { new[] { new { text = "\U0001f4cd Share Location", request_location = true } } }, one_time_keyboard = true, resize_keyboard = true } }); } else if (context.Buttons?.Count > 0) { // Inline keyboard: one button per row. Unlike WhatsApp, Telegram allows mixing // url and callback_data buttons in the same keyboard, so REPLY/URL both fit here. apiUrl = $"{baseUrl}/bot{botToken}/sendMessage"; json = JsonSerializer.Serialize(new { chat_id = recipient, text = context.Message, parse_mode = "HTML", reply_markup = new { inline_keyboard = context.Buttons.Select(b => new[] { BuildInlineButton(b) }) } }); } else { apiUrl = $"{baseUrl}/bot{botToken}/sendMessage"; json = JsonSerializer.Serialize(new { chat_id = recipient, text = context.Message, parse_mode = "HTML" }); } var content = new StringContent(json, Encoding.UTF8, "application/json"); var response = await _httpClient.PostAsync(apiUrl, content, cancellationToken); stopwatch.Stop(); var responseContent = await response.Content.ReadAsStringAsync(cancellationToken); if (!response.IsSuccessStatusCode) { _logger.LogError( "TELEGRAM SEND FAILED | Tenant={Tenant} | StatusCode={StatusCode} | Response={Response}", context.TenantId, response.StatusCode, responseContent); return new EIPChannelResponseDTO { IsSent = false, Status = "FAILED", ErrorMessage = responseContent }; } _logger.LogInformation( "TELEGRAM SEND SUCCESS | Tenant={Tenant} | DurationMs={Duration}", context.TenantId, stopwatch.ElapsedMilliseconds); return new EIPChannelResponseDTO { IsSent = true, ExternalMessageId = Guid.NewGuid().ToString(), Status = "SENT" }; } catch (OperationCanceledException) { stopwatch.Stop(); _logger.LogWarning( "TELEGRAM SEND CANCELLED | Tenant={Tenant} | DurationMs={Duration}", context?.TenantId, stopwatch.ElapsedMilliseconds); throw; } catch (Exception ex) { stopwatch.Stop(); _logger.LogError(ex, "TELEGRAM SEND ERROR | Tenant={Tenant} | DurationMs={Duration}", context?.TenantId, stopwatch.ElapsedMilliseconds); return new EIPChannelResponseDTO { IsSent = false, Status = "FAILED", ErrorMessage = ex.Message }; } } /// /// Telegram inline_keyboard button: url-type buttons open the link directly, everything /// else round-trips via callback_data (capped at Telegram's 64-byte limit). /// private static object BuildInlineButton(EIPButtonDTO button) { var text = button.Title ?? button.Text ?? button.ButtonText ?? button.Id ?? string.Empty; if (string.Equals(button.ButtonType, "URL", StringComparison.OrdinalIgnoreCase) && !string.IsNullOrWhiteSpace(button.Url)) { return new { text, url = button.Url }; } var callbackData = button.Payload ?? button.Id ?? text; if (callbackData.Length > 64) callbackData = callbackData[..64]; return new { text, callback_data = callbackData }; } } }